Trao đổi sạch tất cả các lần xuất hiện của hai chuỗi bằng sed


13

Giả sử tôi có một tệp chứa nhiều lần xuất hiện của cả StringA và StringB. Tôi muốn thay thế tất cả các lần xuất hiện của StringA bằng StringB và (đồng thời) tất cả các lần xuất hiện của StringB bằng StringA.

Ngay bây giờ, tôi đang làm một cái gì đó như

cat file.txt | sed 's/StringB/StringC/g' | sed 's/StringA/StringB/g' | sed 's/StringC/StringA/g'

Vấn đề với cách tiếp cận này là nó giả sử StringC không xảy ra trong tệp. Mặc dù đây không phải là một vấn đề trong thực tế, nhưng giải pháp này vẫn cảm thấy bẩn - nghĩa là, nó cảm thấy giống như một cơ hội để tìm hiểu thêm phép thuật unix. :)

Câu trả lời:


11

Nếu StringBStringAkhông thể xuất hiện trên cùng một dòng đầu vào, thì bạn có thể yêu cầu sed thực hiện thay thế một cách và chỉ thử cách khác nếu không có sự xuất hiện của chuỗi tìm kiếm đầu tiên.

<file.txt sed -e 's/StringA/StringB/g' -e t -e 's/StringB/StringA/g'

Trong trường hợp chung, tôi không nghĩ có một phương pháp dễ dàng trong sed. Nhân tiện, lưu ý rằng đặc tả là mơ hồ nếu StringAStringBcó thể chồng lấp. Đây là một giải pháp Perl, thay thế sự xuất hiện ngoài cùng bên trái của chuỗi và lặp lại.

<file.txt perl -pe 'BEGIN {%r = ("StringA" => "StringB", "StringB" => "StringA")}
                    s/(StringA|StringB)/$r{$1}/ge'

Nếu bạn muốn gắn bó với các công cụ POSIX, awk là cách tốt nhất. Awk không có nguyên thủy cho các thay thế tham số chung, vì vậy bạn cần phải tự lăn.

<file.txt awk '{
    while (match($0, /StringA|StringB/)) {
        printf "%s", substr($0, 1, RSTART-1);
        $0 = substr($0, RSTART);
        printf "%s", /^StringA/ ? "StringB" : "StringA";
        $0 = substr($0, 1+RLENGTH)
    }
    print
}'

Khi tôi chạy lệnh đầu tiên, sed nói với tôi sed: can't read s/StringB/StringA/g: No such file or directory. Có vẻ như -e t PATTERNkhông hiểu rõ lắm.
Tòa nhà chọc trời

1
@Gyscos Có một mất tích -etrước slệnh thứ hai . Tôi đã sửa câu trả lời của mình.
Gilles 'SO- ngừng trở nên xấu xa'

8

Ngay bây giờ, tôi đang làm một cái gì đó như
...............
Vấn đề với cách tiếp cận này là nó giả sử StringC không xảy ra trong tệp.

Tôi nghĩ rằng cách tiếp cận của bạn là tốt, bạn chỉ nên sử dụng một cái gì đó khác thay vì một chuỗi, một cái gì đó không thể xảy ra trong một dòng (trong không gian mẫu). Ứng cử viên tốt nhất là \newline.
Thông thường, không có dòng đầu vào nào trong không gian mẫu sẽ chứa ký tự đó, để hoán đổi tất cả các lần xuất hiện củaTHISTHATtrong một tệp, bạn có thể chạy:

sed 's/THIS/\
/g
s/THAT/THIS/g
s/\n/THAT/g' infile

hoặc, nếu sed của bạn cũng hỗ trợ \ntrong RHS:

sed 's/THIS/\n/g;s/THAT/THIS/g;s/\n/THAT/g' infile

1
Thật là đẹp Tôi đã khóc một chút. Một cách khác để thực hiện các dòng mới RHS là các biến shell - cho dù các sedhỗ trợ thoát nhất định hay không trở nên ít quan trọng hơn nếu bạn chuẩn bị trước một vài macro. Giống như set /THIS /THAT "$(printf \\n/)"; sed "s/$2/\\$4g;s/$3$2/g;s/\\n$3/g"- hơi ngu ngốc ở đây, thừa nhận, nhưng nó có ý nghĩa hơn rất nhiều khi một số thời điểm khác - đặc biệt là đối với các lớp char và tương tự.
mikeerv

Làm thế nào về điều đó, người đàn ông. Thậm chí còn có một câu trả lời về nó. Có phải nó ở đó khi tôi bình luận? Tôi chỉ thấy điều này xuất hiện trong danh sách được chỉnh sửa gần đây (có thể) và dòng trên cùng của câu trả lời hàng đầu là một chút (nếu bạn chỉ quan tâm đến linux không nhúng, tôi đoán vậy) . Tôi thích đề xuất của Gilles ở đó - trừ khi bạn đang thực hiện một cuộc chạy dài sed, ngã ba liên tục với ecơn ác mộng. Trên một lưu ý khác - Tôi đã chơi với pastecả một ngày. Tôi đã thực hiện một trình phân tích cú pháp tùy chọn - giống như columnloại. Nó chỉ tạo các dấu gạch ngang cho các chuỗi đầu vào và các chuỗi kết hợp với nhau.
mikeerv

3

Tôi nghĩ việc sử dụng chuỗi "nonce" để hoán đổi hai từ là hoàn toàn hợp lệ. Nếu bạn muốn một giải pháp tổng quát hơn, bạn có thể làm một cái gì đó như:

sed 's/_/__/g; s/you/x_x/g; s/me/you/g; s/x_x/me/g; s/__/_/g' <<<"say you say me"

Điều đó mang lại

say me say you

Lưu ý rằng bạn cần hai thay thế bổ sung ở đây để tránh thay thế x_xnếu bạn có chuỗi "x_x". Nhưng ngay cả điều đó vẫn có vẻ đơn giản hơn awkgiải pháp cho tôi.


Đó dường như là những gì Asker nói họ đã làm.
roaima

1
Có, tôi đã bỏ qua điều đó lúc đầu (xem lịch sử chỉnh sửa) nhưng giải pháp đã cho của tôi khác vì nó hoạt động ngay cả khi chuỗi thay thế (ở đây "x_x") xảy ra trong chuỗi ban đầu, do đó tổng quát hơn.
David Ongaro

Thông minh, nhưng có một nhược điểm. Nếu StringA hoặc StringB chứa _, người ta cần điều chỉnh _chính nó (chọn một ký tự khác) hoặc chuỗi rắc rối (thực hiện s/_/__/gtrên nó trước, có vẻ tốt hơn). Giải pháp của bạn, vì nó là, không thể được áp dụng một cách mù quáng để hoán đổi các chuỗi tùy ý.
Kamil Maciorowski

@KamilMaciorowski Tôi không hiểu ý bạn là gì? Tôi thực sự áp dụng s/_/__/gtrước. Có lẽ chỉ hiển thị một testcase thất bại.
David Ongaro

@KamilMaciorowski ah Tôi nghĩ giờ tôi đã hiểu. Bạn có nghĩa là nếu chính chuỗi thay thế có chứa a _, vì vậy hãy nói thay thế y_oubằng me. Vâng, đó là sự thật người ta phải nhận thức được điều đó và đưa y__ouvào biểu thức. Một tập lệnh lấy thay thế làm tham số đầu vào cũng phải tính đến điều đó.
David Ongaro
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.