Làm thế nào để thay thế nhiều mẫu cùng một lúc với sed?


231

Giả sử tôi có chuỗi 'abbc' và tôi muốn thay thế:

  • ab -> bc
  • bc -> ab

Nếu tôi thử hai thay thế thì kết quả không như tôi mong muốn:

echo 'abbc' | sed 's/ab/bc/g;s/bc/ab/g'
abab

Vậy tôi có thể sử dụng lệnh sed nào để thay thế như dưới đây?

echo abbc | sed SED_COMMAND
bcab

EDIT : Trên thực tế, văn bản có thể có nhiều hơn 2 mẫu và tôi không biết mình sẽ cần bao nhiêu thay thế. Vì có một câu trả lời nói rằng đó sedlà một trình chỉnh sửa luồng và thay thế nó là tham lam, tôi nghĩ rằng tôi sẽ cần phải sử dụng một số ngôn ngữ kịch bản cho điều đó.


Bạn có cần phải thực hiện nhiều thay thế trên cùng một dòng? Nếu không chỉ thả gcờ từ cả hai s///lệnh đó và nó sẽ hoạt động.
Etan Reisner

Bạn đã bỏ lỡ điểm của câu hỏi của tôi. Ý tôi là bạn cần phải thực hiện mỗi lần thay thế nhiều lần trên cùng một dòng. Có nhiều hơn một trận đấu cho ab hoặc bc trong đầu vào ban đầu.
Etan Reisner

Xin lỗi @EtanReisner tôi đã hiểu lầm, Anwser là có. văn bản có thể có nhiều thay thế.
DaniloNC

Câu trả lời:


342

Có lẽ một cái gì đó như thế này:

sed 's/ab/~~/g; s/bc/ab/g; s/~~/bc/g'

Thay thế ~bằng một ký tự mà bạn biết sẽ không có trong chuỗi.


9
GNU sed xử lý nuls, vì vậy bạn có thể sử dụng \x0cho ~~.
jthill

3
gcần thiết và nó làm gì?
Lee

12
@Lee gdành cho toàn cầu - nó thay thế tất cả các phiên bản của mẫu trong mỗi dòng, thay vì chỉ đầu tiên (đó là hành vi mặc định).
ness101

1
Vui lòng xem câu trả lời của tôi stackoverflow.com/a/41273117/539149 để biết biến thể của câu trả lời ooga có thể thay thế nhiều kết hợp cùng một lúc.
Zack Morris

3
mà bạn biết sẽ không nằm trong chuỗi Đối với mã sản xuất, đừng bao giờ đưa ra bất kỳ giả định nào về đầu vào. Đối với các bài kiểm tra, tốt, các bài kiểm tra không bao giờ thực sự chứng minh tính đúng đắn, nhưng một ý tưởng hay cho bài kiểm tra là: Sử dụng chính tập lệnh làm đầu vào.
hagello

33

Tôi luôn sử dụng nhiều câu lệnh với "-e"

$ sed -e 's:AND:\n&:g' -e 's:GROUP BY:\n&:g' -e 's:UNION:\n&:g' -e 's:FROM:\n&:g' file > readable.sql

Điều này sẽ nối thêm '\ n' trước tất cả AND, NHÓM, UNION và TỪ, trong khi '&' có nghĩa là chuỗi phù hợp và '\ n &' có nghĩa là bạn muốn thay thế chuỗi phù hợp bằng '\ n' trước khi 'khớp' '


14

Dưới đây là một biến thể về câu trả lời của ooga hoạt động cho nhiều cặp tìm kiếm và thay thế mà không phải kiểm tra cách các giá trị có thể được sử dụng lại:

sed -i '
s/\bAB\b/________BC________/g
s/\bBC\b/________CD________/g
s/________//g
' path_to_your_files/*.txt

Đây là một ví dụ:

trước:

some text AB some more text "BC" and more text.

sau:

some text BC some more text "CD" and more text.

Lưu ý rằng \bbiểu thị ranh giới từ, đó là điều ngăn cản việc ________can thiệp vào tìm kiếm (Tôi đang sử dụng GNU sed 4.2.2 trên Ubuntu). Nếu bạn không sử dụng tìm kiếm ranh giới từ, thì kỹ thuật này có thể không hoạt động.

Cũng lưu ý rằng điều này mang lại kết quả tương tự như loại bỏ s/________//gvà nối thêm && sed -i 's/________//g' path_to_your_files/*.txtvào cuối lệnh, nhưng không yêu cầu chỉ định đường dẫn hai lần.

Một biến thể chung về điều này sẽ là sử dụng \x0hoặc _\x0_thay thế ________nếu bạn biết rằng không có null nào xuất hiện trong các tệp của bạn, như jthill đề xuất .


Tôi đồng ý với nhận xét của hagello ở trên về việc không đưa ra các giả định về những gì đầu vào có thể chứa. Do đó, cá nhân tôi cảm thấy rằng đây là giải pháp đáng tin cậy nhất, ngoài việc đặt ống hút lên nhau ( sed 's/ab/xy/' | sed 's/cd/ab/' .....)
leetbacoon

12

sedlà một biên tập viên dòng. Nó tìm kiếm và thay thế một cách tham lam. Cách duy nhất để làm những gì bạn yêu cầu là sử dụng một mẫu thay thế trung gian và cuối cùng thay đổi nó.

echo 'abcd' | sed -e 's/ab/xy/;s/cd/ab/;s/xy/cd/'


4

Điều này có thể làm việc cho bạn (GNU sed):

sed -r '1{x;s/^/:abbc:bcab/;x};G;s/^/\n/;:a;/\n\n/{P;d};s/\n(ab|bc)(.*\n.*:(\1)([^:]*))/\4\n\2/;ta;s/\n(.)/\1\n/;ta' file

Điều này sử dụng một bảng tra cứu được chuẩn bị và giữ trong không gian giữ (HS) và sau đó được thêm vào mỗi dòng. Một điểm đánh dấu duy nhất (trong trường hợp này \n) được thêm vào đầu dòng và được sử dụng như một phương pháp để tìm kiếm dọc theo chiều dài của dòng. Khi điểm đánh dấu đến cuối dòng, quá trình kết thúc và được in ra bảng tra cứu và các điểm đánh dấu bị loại bỏ.

NB Bảng tra cứu được đặt trước ngay từ đầu và một điểm đánh dấu duy nhất thứ hai (trong trường hợp này :) được chọn để không xung đột với các chuỗi thay thế.

Với một số ý kiến:

sed -r '
  # initialize hold with :abbc:bcab
  1 {
    x
    s/^/:abbc:bcab/
    x
  }

  G        # append hold to patt (after a \n)

  s/^/\n/  # prepend a \n

  :a

  /\n\n/ {
    P      # print patt up to first \n
    d      # delete patt & start next cycle
  }

  s/\n(ab|bc)(.*\n.*:(\1)([^:]*))/\4\n\2/
  ta       # goto a if sub occurred

  s/\n(.)/\1\n/  # move one char past the first \n
  ta       # goto a if sub occurred
'

Bảng hoạt động như thế này:

   **   **   replacement
:abbc:bcab
 **   **     pattern

3

Có thể là một cách tiếp cận đơn giản hơn cho sự xuất hiện của một mẫu mà bạn có thể thử như dưới đây: echo 'abbc' | sed 's / ab / bc /; s / bc / ab / 2'

Đầu ra của tôi:

 ~# echo 'abbc' | sed 's/ab/bc/;s/bc/ab/2'
 bcab

Đối với nhiều lần xuất hiện của mẫu:

sed 's/\(ab\)\(bc\)/\2\1/g'

Thí dụ

~# cat try.txt
abbc abbc abbc
bcab abbc bcab
abbc abbc bcab

~# sed 's/\(ab\)\(bc\)/\2\1/g' try.txt
bcab bcab bcab
bcab bcab bcab
bcab bcab bcab

Hi vọng điêu nay co ich !!


2

Tcl đã tích hợp sẵn cho việc này

$ tclsh
% string map {ab bc bc ab} abbc
bcab

Điều này hoạt động bằng cách đi bộ chuỗi một ký tự tại một thời điểm thực hiện so sánh chuỗi bắt đầu từ vị trí hiện tại.

Trong perl:

perl -E '
    sub string_map {
        my ($str, %map) = @_;
        my $i = 0;
        while ($i < length $str) {
          KEYS:
            for my $key (keys %map) {
                if (substr($str, $i, length $key) eq $key) {
                    substr($str, $i, length $key) = $map{$key};
                    $i += length($map{$key}) - 1;
                    last KEYS;
                }
            }
            $i++;
        }
        return $str;
    }
    say string_map("abbc", "ab"=>"bc", "bc"=>"ab");
'
bcab

0

Đây là một awkdựa trên oogassed

echo 'abbc' | awk '{gsub(/ab/,"xy");gsub(/bc/,"ab");gsub(/xy/,"bc")}1'
bcab
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.