Bagaimana cara mengganti beberapa pola sekaligus dengan sed?

231

Misalkan saya memiliki string 'abbc' dan saya ingin mengganti:

  • ab -> bc
  • bc -> ab

Jika saya mencoba dua menggantikan hasilnya bukan yang saya inginkan:

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

Jadi apa perintah sed yang bisa saya gunakan untuk mengganti seperti di bawah ini?

echo abbc | sed SED_COMMAND
bcab

EDIT : Sebenarnya teks bisa memiliki lebih dari 2 pola dan saya tidak tahu berapa banyak pengganti yang akan saya butuhkan. Karena ada jawaban yang mengatakan bahwa itu sedadalah editor aliran dan penggantinya rakus saya pikir saya perlu menggunakan beberapa bahasa skrip untuk itu.

DaniloNC
sumber
Apakah Anda perlu melakukan beberapa penggantian pada baris yang sama? Jika tidak hanya menjatuhkan gbendera dari kedua s///perintah itu dan itu akan berhasil.
Etan Reisner
Anda melewatkan inti pertanyaan saya. Maksud saya apakah Anda perlu membuat setiap penggantian lebih dari satu kali pada baris yang sama. Apakah ada lebih dari satu kecocokan untuk ab atau bc pada input asli.
Etan Reisner
Maaf @EtanReisner saya salah paham, jawabannya adalah ya. teks dapat memiliki beberapa penggantian.
DaniloNC

Jawaban:

342

Mungkin kira-kira seperti ini:

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

Ganti ~dengan karakter yang Anda tahu tidak akan ada di string.

ooga
sumber
9
GNU sed menangani nuls, jadi Anda bisa menggunakannya \x0untuk ~~.
jthill
3
Apakah gperlu dan apa fungsinya?
Lee
12
@Lee gadalah untuk global - ia menggantikan semua instance dari pola di setiap baris, bukan hanya yang pertama (yang merupakan perilaku default).
naught101
1
Silakan lihat jawaban saya stackoverflow.com/a/41273117/539149 untuk variasi jawaban Ooga yang dapat menggantikan banyak kombinasi secara bersamaan.
Zack Morris
3
yang Anda tahu tidak akan ada dalam string Untuk kode produksi, jangan pernah membuat asumsi tentang input. Untuk tes, well, tes tidak pernah benar-benar membuktikan kebenaran, tetapi ide yang baik untuk tes adalah: Gunakan skrip itu sendiri sebagai input.
hagello
33

Saya selalu menggunakan banyak pernyataan dengan "-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

Ini akan menambahkan '\ n' sebelum semua AND, GROUP BY, UNION dan FROM's, sedangkan '&' berarti string yang cocok dan '\ n &' berarti Anda ingin mengganti string yang cocok dengan '\ n' sebelum 'cocok' '

Paulo Henrique Lellis Gonalves
sumber
14

Berikut adalah variasi jawaban ooga yang berfungsi untuk beberapa pencarian dan mengganti pasangan tanpa harus memeriksa bagaimana nilai dapat digunakan kembali:

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

Berikut ini sebuah contoh:

sebelum:

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

setelah:

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

Perhatikan yang \bmenunjukkan batas kata, yang mencegahnya________ dari mengganggu pencarian (Saya menggunakan GNU sed 4.2.2 di Ubuntu). Jika Anda tidak menggunakan pencarian batas kata, maka teknik ini mungkin tidak berfungsi.

Perhatikan juga bahwa ini memberikan hasil yang sama seperti menghapus s/________//gdan menambahkan && sed -i 's/________//g' path_to_your_files/*.txtke akhir perintah, tetapi tidak mengharuskan menentukan jalur dua kali.

Variasi umum untuk ini adalah menggunakan \x0atau _\x0_menggantikan ________jika Anda tahu bahwa tidak ada null yang muncul di file Anda, seperti yang disarankan jthill .

Zack Morris
sumber
Saya setuju dengan komentar hagello di atas tentang tidak membuat asumsi tentang apa yang mungkin berisi input. Oleh karena itu, saya pribadi merasa bahwa ini adalah solusi yang paling dapat diandalkan, selain dari memompa seds di atas satu sama lain ( sed 's/ab/xy/' | sed 's/cd/ab/' .....)
leetbacoon
12

sedadalah editor aliran. Ia mencari dan mengganti dengan rakus. Satu-satunya cara untuk melakukan apa yang Anda minta adalah menggunakan pola substitusi antara dan mengubahnya kembali pada akhirnya.

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

kuriouscoder
sumber
4

Ini mungkin bekerja untuk Anda (sed GNU):

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

Ini menggunakan tabel pencarian yang disiapkan dan diadakan di ruang penahanan (HS) dan kemudian ditambahkan ke setiap baris. Penanda unik (dalam hal ini\n ) diawali dengan awal baris dan digunakan sebagai metode untuk menabrak-sepanjang pencarian sepanjang garis. Setelah penanda mencapai ujung garis proses selesai dan dicetak tabel pencarian dan spidol dibuang.

NB Tabel pencarian disiapkan pada awal dan penanda unik kedua (dalam hal ini :) dipilih agar tidak berbenturan dengan string substitusi.

Dengan beberapa komentar:

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
'

Tabelnya berfungsi seperti ini:

   **   **   replacement
:abbc:bcab
 **   **     pattern
potong
sumber
3

Mungkin pendekatan yang lebih sederhana untuk kejadian pola tunggal yang dapat Anda coba seperti di bawah ini: echo 'abbc' | sed 's / ab / bc /; s / bc / ab / 2'

Output saya:

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

Untuk beberapa kejadian pola:

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

Contoh

~# 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

Semoga ini membantu !!

dst_91
sumber
2

Tcl memiliki builtin untuk ini

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

Ini berfungsi dengan menggerakkan string karakter pada satu waktu melakukan perbandingan string mulai dari posisi saat ini.

Dalam 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
glenn jackman
sumber
0

Ini awkberdasarkan oogassed

echo 'abbc' | awk '{gsub(/ab/,"xy");gsub(/bc/,"ab");gsub(/xy/,"bc")}1'
bcab
Jotne
sumber