Ganti string yang berisi karakter baris baru

10

Dengan bashshell, dalam file dengan baris seperti yang berikut ini

first "line"
<second>line and so on

Saya ingin mengganti satu atau lebih kejadian "line"\n<second>dengan other charactersdan memperoleh setiap waktu:

first other characters line and so on

Jadi saya harus mengganti string dengan karakter khusus seperti "dan <dan dengan karakter baris baru.

Setelah mencari di antara jawaban yang lain, saya menemukan bahwa seddapat menerima baris baru di sisi kanan perintah (jadi, other charactersstring), tetapi tidak di sebelah kiri.

Apakah ada cara (lebih sederhana dari ini ) untuk mendapatkan hasil ini dengan sedatau grep?

BowPark
sumber
Apakah Anda bekerja dengan mac? yang \npernyataan ewline Anda membuat sebabnya saya bertanya. orang jarang bertanya apakah mereka bisa melakukan s//\n/apa yang Anda bisa dengan GNU sed, meskipun kebanyakan orang lain sedakan menolak pelarian itu di sisi kanan. tetap saja, jalan \nkeluar akan bekerja di sebelah kiri dalam POSIX apa pun seddan Anda dapat menerjemahkannya dengan mudah seolah- y/c/\n/olah itu akan memiliki efek yang sama seperti s/c/\n/gdan karenanya tidak selalu berguna.
mikeserv

Jawaban:

3

Tiga sedperintah berbeda :

sed '$!N;s/"[^"]*"\n<[^>]*>/other characters /;P;D'

sed -e :n -e '$!N;s/"[^"]*"\n<[^>]*>/other characters /;tn'

sed -e :n -e '$!N;/"$/{$!bn' -e '};s/"[^"]*"\n<[^>]*>/other characters /g'

Mereka bertiga membangun di atas s///perintah ubstitusi dasar :

s/"[^"]*"\n<[^>]*>/other characters /

Mereka juga semua berusaha untuk berhati-hati dalam menangani baris terakhir, karena sedcenderung berbeda pada output mereka dalam kasus tepi. Inilah artinya $!alamat yang cocok dengan setiap baris yang !bukan yang $terakhir.

Mereka juga semua menggunakan Nperintah ext untuk menambahkan baris input berikutnya ke pola ruang mengikuti \nkarakter ewline. Siapa pun yang telah lama sedbelajar akan belajar untuk bergantung pada \nkarakter ewline - karena satu-satunya cara untuk mendapatkannya adalah dengan meletakkannya secara eksplisit di sana.

Ketiganya berusaha untuk membaca input sesedikit mungkin sebelum mengambil tindakan - sedbertindak secepat mungkin dan tidak perlu membaca seluruh file input sebelum melakukannya.

Meskipun mereka melakukan semuanya N, ketiganya berbeda dalam metode rekursi mereka.

Perintah Pertama

Perintah pertama menggunakan N;P;Dloop yang sangat sederhana . Tiga perintah ini terintegrasi untuk POSIX-compatible seddan saling melengkapi satu sama lain dengan baik.

  • N- seperti yang telah disebutkan, menambahkan Nbaris input ext ke pola-ruang setelah \npembatas ewline yang dimasukkan .
  • P- seperti p; itu Pmemecah pola-ruang - tetapi hanya sampai dengan \nkarakter ewline pertama yang terjadi . Maka, diberi input / perintah berikut:

    • printf %s\\n one two | sed '$!N;P;d'
  • sed Phanya satu . Namun, dengan ...

  • D- seperti d; itu Dmenghapus pola-ruang dan memulai siklus baris lain. Tidak seperti d , Dmenghapus hanya sampai garis tepi pertama yang terjadi \ndi pola-ruang. Jika ada lebih banyak ruang-pola mengikuti \nkarakter ewline, sedmulailah siklus baris berikutnya dengan yang tersisa. Jika ddalam contoh sebelumnya diganti dengan D, misalnya, sedakan Pmemecah satu dan dua .

Perintah ini hanya muncul untuk baris yang tidak cocok dengan s///pernyataan ubstitusi. Karena s///ubstitusi menghapus \newline yang ditambahkan N, tidak pernah ada yang tersisa ketika sed Dmenghapus pola-ruang.

Tes dapat dilakukan untuk menerapkan Pdan / atau Dsecara selektif, tetapi ada perintah lain yang lebih sesuai dengan strategi itu. Karena rekursi ini dilaksanakan untuk menangani garis berturut-turut yang cocok hanya bagian dari aturan pengganti, urutan berturut-turut dari garis pencocokan kedua ujung dari s///ubstitution tidak bekerja dengan baik .:

Diberikan masukan ini:

first "line"
<second>"line"
<second>"line"
<second>line and so on

... itu mencetak ...

first other characters "line"
<second>other characters line and so on

Namun, itu menangani

first "line"
second "line"
<second>line

...baik baik saja.

Perintah Kedua

Perintah ini sangat mirip dengan yang ketiga. Keduanya menggunakan label :bpeternakan / test (seperti juga ditunjukkan dalam jawaban Joeseph R. di sini ) dan kembali lagi ke sana dengan syarat tertentu.

  • -e :n -e- sedskrip portabel akan membatasi :definisi label dengan \newline atau -epernyataan xecution inline baru .
    • :n- mendefinisikan label bernama n. Ini dapat dikembalikan kapan saja dengan bnatau tn.
  • tn- tperintah est kembali ke label yang ditentukan (atau, jika tidak ada yang disediakan, keluar dari skrip untuk siklus baris saat ini) jika ada s///pengganti karena label itu ditentukan atau karena yang terakhir disebut tests berhasil.

Dalam perintah ini rekursi terjadi untuk garis yang cocok. Jika sedberhasil mengganti pola dengan karakter lain , sedkembali ke :nlabel dan coba lagi. Jika tidak terjadi s///ubstitusi, cetak sedpola-ruang dan mulailah siklus-baris berikutnya.

Ini cenderung menangani urutan berturut-turut dengan lebih baik. Di mana yang terakhir gagal, ini mencetak:

first other characters other characters other characters line and so on

Perintah Ketiga

Seperti disebutkan, logika di sini sangat mirip dengan yang terakhir, tetapi tes lebih eksplisit.

  • /"$/bn- ini adalah sedujian. Karena bperintah ranch adalah fungsi dari alamat ini, sedhanya akan branch kembali ke :nsetelah \newline ditambahkan dan pola-ruang masih berakhir dengan "tanda kutip ganda.

Ada sedikit yang dilakukan antara Ndan bmungkin - dengan cara ini seddapat dengan cepat mengumpulkan input sebanyak yang diperlukan untuk memastikan bahwa baris berikut tidak sesuai dengan aturan Anda. The s///ubstitution berbeda di sini bahwa itu mempekerjakan gbendera lobal - dan sehingga akan melakukan semua penggantian diperlukan sekaligus. Diberikan input identik perintah ini menghasilkan identik dengan yang terakhir.

mikeserv
sumber
Maaf untuk pertanyaan sepele, tetapi apa artinya DATAdan bagaimana Anda menerima input teks?
BowPark
@BowPark - Dalam contoh <<\DATA\ntext input\nDATA\nini dipanggang, tetapi itu hanya teks yang diserahkan sedoleh shell dalam dokumen di sini . Ini akan berfungsi seperti sed 'script' filenameatau process that writes to stdout | sed 'script'. Apakah itu membantu?
mikeserv
Ya, terima kasih! Mengapa tanpa Dsetiap baris yang diubah ganda? (Anda menggunakannya sesuai keperluan; mungkin saya tidak tahu sedpersis)
BowPark
1
@BowPark - Anda mendapatkan dua kali lipat ketika menghapus Dkarena Djika tidak Dmenghapus dari apa yang sekarang Anda lihat menjadi dua kali lipat. Saya baru saja mengedit - dan saya akan segera mengembangkannya.
mikeserv
1
@BowPark - ok, saya telah memperbaruinya dan memberikan opsi. Mungkin sedikit lebih mudah untuk membaca / mengerti sekarang. Saya juga secara eksplisit membahas Dhal itu.
mikeserv
7

Yah, saya bisa memikirkan beberapa cara sederhana tetapi tidak melibatkan grep(yang tidak melakukan pergantian tetap) atau sed.

  1. Perl

    Untuk mengganti setiap kemunculan "line"\n<second>dengan other characters, gunakan:

    $ perl -00pe 's/"line"\n<second>/other characters /g' file
    first other characters line and so on
    

    Atau, untuk memperlakukan beberapa kejadian berturut-turut "line"\n<second>sebagai satu, dan ganti semuanya dengan satu other characters, gunakan:

    perl -00pe 's/(?:"line"\n<second>)+/other characters /g' file
    

    Contoh:

    $ cat file
    first "line"
    <second>"line"
    <second>"line"
    <second>line and so on
    $ perl -00pe 's/(?:"line"\n<second>)+/other characters /g' file
    first other characters line and so on
    

    The -00menyebabkan Perl untuk membaca file dalam "modus ayat" yang berarti bahwa "garis" didefinisikan oleh \n\nbukan \n, pada dasarnya, setiap paragraf diperlakukan sebagai garis. Substitusi karena itu cocok dengan satu baris baru.

  2. awk

    $  awk -v RS="\n\n" -v ORS="" '{
          sub(/"line"\n<second>/,"other characters ", $0)
          print;
        }' file 
    first other characters line and so on
    

    Ide dasar yang sama, kita mengatur pemisah rekaman ( RS) untuk \n\nmenghirup seluruh file, kemudian pemisah catatan keluaran menjadi nol (jika tidak, baris baru tambahan dicetak) dan kemudian menggunakan sub()fungsi untuk melakukan penggantian.

terdon
sumber
2
@ mikeserv? Yang mana? Yang kedua seharusnya, OP mengatakan mereka ingin "mengganti satu atau lebih kejadian", jadi makan paragraf mungkin apa yang mereka harapkan.
terdon
poin yang sangat bagus. Saya kira saya lebih fokus dan memperoleh setiap waktu , tapi saya kira tidak jelas apakah itu harus satu penggantian per kejadian atau satu penggantian per urutan kejadian ... @BowPark?
mikeserv
Diperlukan satu penggantian per kejadian.
BowPark
@BowPark OK, maka pendekatan perl pertama atau awk keduanya harus bekerja. Bukankah mereka memberi Anda hasil yang diinginkan?
terdon
Ini bekerja, terima kasih, tetapi yang ketiga awkseharusnya print;}' file. Saya perlu menghindari Perl dan sebaiknya menggunakan sed, toh Anda menyarankan alternatif yang baik.
BowPark
6

baca seluruh file dan lakukan penggantian global:

sed -n 'H; ${x; s/"line"\n<second>/other characters /g; p}' <<END
first "line"
<second> line followed by "line"
<second> and last
END
first other characters  line followed by other characters  and last
glenn jackman
sumber
Iya. Ini bekerja, tetapi bagaimana jika saya memiliki beberapa kejadian?
BowPark
Hah, benar. Memperbaiki
glenn jackman
1
maaf untuk nitpick lagi, tetapi ${cmds}khusus-GNU - kebanyakan yang lain sedakan memerlukan \newline atau -ejeda antara pdan }. Anda dapat menghindari tanda kurung sama sekali - dan mudah dibawa - dan bahkan menghindari memasukkan \nkarakter ewline tambahan pada baris pertama seperti:sed 'H;1h;$!d;x;s/"line"\n<second>/other characters /g'
mikeserv
Saya mengujinya dan sepertinya tidak portabel. Ini mencetak baris baru tambahan di awal output, tetapi hasilnya benar pada GNU.
BowPark
Untuk menghapus baris baru: sed -n '1{h;n};H; ${x; s/"line"\n<second>/other characters /g; p}'- namun ini semakin tidak dapat dipelihara.
glenn jackman
3

Berikut varian jawaban glenn yang akan berfungsi jika Anda memiliki beberapa kejadian berurutan (hanya bekerja dengan GNU sed):

sed ':x /"line"/N;s/"line"\n<second>/other characters/;/"line"/bx' your_file

Ini :xhanya label untuk percabangan. Pada dasarnya, yang dilakukan adalah memeriksa baris setelah substitusi dan jika masih cocok "line", itu bercabang kembali ke :xlabel (itulah yang bxdilakukan) dan menambahkan baris lain ke buffer dan mulai memprosesnya.

Joseph R.
sumber
@ mikeserv Harap spesifik tentang apa yang Anda maksud. Ini berhasil untuk saya.
Joseph R.
@ mikeserv Maaf, saya benar-benar tidak tahu apa yang Anda bicarakan. Saya menyalin baris kode di atas kembali ke terminal saya dan itu berfungsi dengan benar.
Joseph R.
1
ditarik - ini tampaknya bekerja di GNU sedyang mengambil penanganan label non-POSIX cukup jauh untuk menerima ruang sebagai pembatas untuk deklarasi label. Anda harus mencatat, bahwa yang lain sedakan gagal di sana - dan akan gagal N. GNU sedmemecah pedoman POSIX untuk mencetak pola-ruang sebelum berhenti pada Npada baris terakhir, tetapi POSIX menjelaskan bahwa jika suatu Nperintah dibaca pada baris terakhir tidak ada yang harus dicetak.
mikeserv
Jika Anda mengedit posting untuk menentukan GNU saya akan membalikkan suara saya dan menghapus komentar ini. Juga, mungkin perlu belajar tentang vperintah GNU yang saling bertentangan sedtetapi tidak ada op dalam versi GNU 4 dan lebih tinggi.
mikeserv
1
dalam hal ini saya akan menawarkan satu lagi - ini dapat dilakukan portable seperti: sed -e :x -e '/"line"/{$!N' -e '};s/"line"\n<second>/other characters/;/"line"/bx'.
mikeserv