Tukar dengan bersih semua kejadian dua string menggunakan sed

13

Misalkan saya memiliki file yang berisi banyak kemunculan StringA dan StringB. Saya ingin mengganti semua kemunculan StringA dengan StringB, dan (secara bersamaan) semua kemunculan StringB dengan StringA.

Saat ini, saya sedang melakukan sesuatu seperti

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

Masalah dengan pendekatan ini adalah bahwa ia menganggap StringC tidak terjadi dalam file. Meskipun ini bukan masalah dalam praktiknya, solusi ini masih terasa kotor - yaitu, rasanya seperti kesempatan untuk mempelajari lebih banyak unix magic. :)

Seth
sumber

Jawaban:

11

Jika StringBdan StringAtidak dapat muncul pada jalur input yang sama, maka Anda dapat meminta sed untuk melakukan penggantian satu arah, dan hanya coba sebaliknya jika tidak ada kemunculan string yang dicari pertama kali.

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

Dalam kasus umum, saya tidak berpikir ada metode yang mudah di sed. By the way, perhatikan bahwa spesifikasinya ambigu jika StringAdan StringBbisa tumpang tindih. Inilah solusi Perl, yang menggantikan kemunculan paling kiri dari string, dan diulang.

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

Jika Anda ingin tetap menggunakan alat POSIX, awk adalah caranya. Awk tidak memiliki primitif untuk penggantian parametrized umum, jadi Anda perlu memutar sendiri.

<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
}'
Gilles 'SANGAT berhenti menjadi jahat'
sumber
Ketika saya menjalankan perintah pertama, sed memberitahu saya sed: can't read s/StringB/StringA/g: No such file or directory. Sepertinya -e t PATTERNtidak dipahami dengan baik.
Gyscos
1
@Gyscos Ada yang hilang -esebelum sperintah kedua . Saya sudah memperbaiki jawaban saya.
Gilles 'SANGAT berhenti menjadi jahat'
8

Saat ini, saya sedang melakukan sesuatu seperti
...............
Masalah dengan pendekatan ini adalah mengasumsikan StringC tidak terjadi dalam file.

Saya pikir pendekatan Anda baik-baik saja, Anda hanya perlu menggunakan sesuatu yang lain daripada string, sesuatu yang tidak dapat terjadi dalam garis (dalam ruang pola). Kandidat terbaik adalah \newline.
Biasanya, tidak ada baris input dalam ruang pola yang akan mengandung karakter itu sehingga, untuk menukar semua kemunculan THISdan THATdalam file, Anda bisa menjalankan:

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

atau, jika sed Anda mendukung \nRHS juga:

sed 's/THIS/\n/g;s/THAT/THIS/g;s/\n/THAT/g' infile
don_crissti
sumber
1
Ini indah. Saya menangis sedikit. Cara lain untuk melakukan baris baru RHS adalah variabel shell - apakah sedmendukung pelolosan tertentu atau tidak menjadi jauh lebih penting jika Anda menyiapkan beberapa makro sebelumnya. Seperti set /THIS /THAT "$(printf \\n/)"; sed "s/$2/\\$4g;s/$3$2/g;s/\\n$3/g"- agak bodoh di sini, memang, tapi itu jauh lebih masuk akal ketika beberapa kali - terutama untuk kelas char dan sejenisnya.
mikeserv
Nah bagaimana dengan itu, bung. Bahkan ada jawaban di sana tentang itu. Apakah ada di sana ketika saya berkomentar? Saya baru saja melihat hal yang muncul pada daftar yang baru diedit (mungkin) dan baris teratas dari jawaban atas sedikit tidak aktif (jika Anda hanya peduli tentang linux yang tidak tertanam, saya kira) . Saya lebih suka saran Gilles di sana - kecuali Anda melakukan jangka panjang sed, garpu konstan di atas eadalah kinduva nightmare. Pada nada yang berbeda - Saya sudah bermain dengan pastesepanjang hari. Saya membuat parser pilihan - seperti column. Itu hanya garis gens untuk string input dan hal-hal string bersama-sama.
mikeserv
3

Saya pikir ini sangat valid untuk menggunakan string "nonce" untuk bertukar dua kata. Jika Anda menginginkan solusi yang lebih umum, Anda dapat melakukan sesuatu seperti:

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

Itu menghasilkan

say me say you

Perhatikan bahwa Anda memerlukan dua subtitusi tambahan di sini untuk menghindari penggantian x_xjika Anda memiliki string "x_x". Tetapi itu pun masih tampak lebih sederhana daripada awksolusi bagi saya.

David Ongaro
sumber
Tampaknya itulah yang dikatakan Penanya yang sudah mereka lakukan.
roaima
1
Ya, saya mengabaikannya pada awalnya (lihat histori pengeditan) tetapi solusi yang saya berikan berbeda karena ia bekerja bahkan ketika string pengganti (di sini "x_x") muncul di string asli, maka itu lebih umum.
David Ongaro
Cerdas, tapi ada tangkapan. Jika StringA atau StringB berisi _, seseorang perlu menyesuaikan _sendiri (pilih karakter lain) atau string yang merepotkan (tampil s/_/__/gdi atasnya sebelumnya, tampaknya lebih baik). Solusi Anda, sebagaimana adanya, tidak dapat diterapkan secara membabi buta untuk menukar string yang sewenang-wenang.
Kamil Maciorowski
@KamilMaciorowski Saya tidak mengerti maksud Anda? Saya benar-benar menerapkan s/_/__/gsebelumnya. Mungkin hanya menunjukkan testcase yang gagal.
David Ongaro
@KamilMaciorowski ah saya pikir saya mengerti sekarang. Maksud Anda jika string pengganti itu sendiri mengandung a _, maka katakanlah ganti y_oudengan me. Ya itu benar, kita harus menyadari hal itu dan y__oumengungkapkannya. Sebuah skrip yang mengambil penggantian sebagai parameter input juga harus memperhitungkannya.
David Ongaro