Bingung dengan output sed ketika menggunakan N. Dapatkah seseorang menjelaskan hasil ini?

8

Saya belajar sed. Semuanya tampak baik-baik saja sampai saya menemukan N (multi-line next). Saya membuat file ini (guide.txt) untuk keperluan latihan / pemahaman / konteks. Berikut adalah isi file tersebut ...

This guide is meant to walk you through a day as a Network
Administrator. By the end, hopefully you will be better
equipped to perform your duties as a Network Administrator
and maybe even enjoy being a Network Administrator that much more.
Network Administrator
Network Administrator
I'm a Network Administrator

Jadi tujuan saya adalah mengganti SEMUA instance dari "Administrator Jaringan" dengan "Pengguna Sistem". Karena instance pertama dari "Administrator Jaringan" dipisahkan oleh baris baru (\ n) Saya perlu operator multi-line berikutnya (N) untuk menambahkan baris yang dimulai dengan "Administrator" dengan baris sebelumnya yang berakhir dengan "Jaringan \ n" . Tidak masalah. Tapi saya juga ingin menangkap semua instance "Network Administrator" lainnya.

Dari penelitian saya, saya telah belajar bahwa saya akan membutuhkan dua perintah substitusi; satu untuk string yang dipisahkan baris baru dan satu untuk yang lain. Juga, ada beberapa jive terjadi karena baris terakhir berisi pertandingan substitusi dan multi-baris berikutnya. Jadi saya membuat ini ...

$ sed '
> s/Network Administrator/System User/
> N
> s/Network\nAdministrator/System\nUser/
> ' guide.txt

Ini mengembalikan hasil ini ...

This guide is meant to walk you through a day as a System
User. By the end, hopefully you will be better
equipped to perform your duties as a System User
and maybe even enjoy being a Network Administrator that much more.
System User
Network Administrator
I'm a System User

Saya berpikir bahwa penggantian satu baris akan menangkap semua contoh "normal" dari "Administrator Jaringan" dan menukar dengan "Pengguna Sistem", sedangkan pernyataan multi-baris akan bekerja dengan baik pada instance yang dipisahkan baris baru, tetapi karena Anda dapat melihatnya dikembalikan, apa yang saya anggap, hasil yang tidak terduga.

Setelah beberapa mengutak-atik, saya mendarat di ...

$ sed '
> s/Network Administrator/System User/
> N
> s/Network\nAdministrator/System\nUser/
> s/Network Administrator/System User/
> ' guide.txt

Dan voila, saya mendapatkan output yang diinginkan dari ...

This guide is meant to walk you through a day as a System
User. By the end, hopefully you will be better
equipped to perform your duties as a System User
and maybe even enjoy being a System User that much more.
System User
System User
I'm a System User

Mengapa ini bekerja dan skrip sed aslinya tidak? Saya benar-benar ingin memahami ini.

Terima kasih sebelumnya atas bantuannya.

dlowrie290
sumber
Bagus untukmu karena belajar Sed! Saya menggunakan Sed untuk menyelesaikan pertanyaan yang sangat mirip sebelumnya di situs ini; mungkin menarik.
Wildcard
Dan dua contoh Sed yang rumit lainnya: unix.stackexchange.com/a/277375/135943 , unix.stackexchange.com/a/257913/135943
Wildcard

Jawaban:

6

Saat Anda sedang belajar sed, saya akan meluangkan waktu untuk menambahkan jawaban @ John1024:

1) Harap dicatat bahwa Anda menggunakan \nstring pengganti. Ini berfungsi di GNU sed, tetapi bukan bagian dari POSIX, jadi ia akan memasukkan backslash dan ndalam banyak lainnya sed(menggunakan \ndalam pola portabel, btw).

Alih-alih ini saya sarankan untuk melakukan s/Network\([[:space:]]\)Administrator/System\1Us‌​er/g: [[:space:]]Akan cocok dengan baris baru atau spasi, jadi Anda tidak perlu dua sperintah, tetapi menggabungkannya dalam satu. Dengan mengelilinginya \(...\)Anda dapat merujuknya di pengganti: \1Akan diganti dengan apa pun yang cocok di pasangan pertama \(\).

2) Untuk mencocokkan pola dengan lebih dari dua garis, Anda harus tahu N;P;Dpolanya:

 sed '$!N;s/Network\([[:space:]]\)Administrator/System\1User/g;P;D'

Itu Nselalu menambahkan baris berikutnya (kecuali untuk baris terakhir, itu sebabnya itu "disapa" dengan $!(= jika bukan baris terakhir; Anda harus selalu mempertimbangkan untuk mengawali Ndengan $!menghindari skrip yang diakhiri secara tidak sengaja). Kemudian setelah penggantian Pcetak saja baris pertama dalam ruang pola dan Dmenghapus baris ini dan memulai siklus berikutnya dengan sisa-sisa ruang pola (tanpa membaca baris berikutnya). Ini mungkin apa yang awalnya Anda maksudkan.

Ingat pola ini, Anda akan sering membutuhkannya.

3) Pola lain yang berguna untuk pengeditan multiline, terutama ketika lebih dari dua baris terlibat: Tahan pengumpulan ruang, seperti yang saya sarankan kepada John:

sed 'H;1h;$!d;g;s/Network\([[:space:]]\)Administrator/System\1Us‌​er/g'

Saya ulangi untuk menjelaskannya: Hmenambahkan setiap baris ke ruang tunggu. Karena ini akan menghasilkan baris baru tambahan sebelum baris pertama, baris pertama harus dipindahkan daripada ditambahkan 1h. Yang berikut ini $!dberarti "untuk semua baris kecuali yang terakhir, hapus spasi pola dan mulai lagi dari awal". Dengan demikian, sisa skrip hanya dieksekusi untuk baris terakhir. Pada titik ini, seluruh file dikumpulkan di ruang penahanan (jadi jangan gunakan ini untuk file yang sangat besar!) Dan gpindahkan ke ruang pola, jadi Anda bisa melakukan semua penggantian sekaligus seperti Anda bisa dengan -zopsi untuk GNU sed.

Ini adalah pola lain yang berguna yang saya sarankan untuk diingat.

Filipos
sumber
Wow! Penjelasan hebat! Ini ditambah dengan jawaban John benar-benar memberi saya wawasan yang lebih baik untuk masalah ini dan secara umum. Sepertinya saya harus belajar lebih banyak. Saya berharap saya bisa memeriksa kedua solusi Anda sebagai jawaban. Terima kasih banyak atas upaya Anda. Mereka sangat dihargai.
dlowrie290
7

Pertama, perhatikan bahwa solusi Anda tidak benar-benar berfungsi. Pertimbangkan file uji ini:

$ cat test1
Network
Administrator Network
Administrator

Dan kemudian jalankan perintah:

$ sed '
 s/Network Administrator/System User/
 N
 s/Network\nAdministrator/System\nUser/
 s/Network Administrator/System User/
 ' test1
System
User Network
Administrator

Masalahnya adalah bahwa kode tidak menggantikan yang terakhir Network\nAdministrator.

Solusi ini berfungsi:

$ sed ':a; /Network$/{$!{N;ba}}; s/Network\nAdministrator/System\nUser/g; s/Network Administrator/System User/g' test1
System
User System
User

Kami juga dapat menerapkan ini pada Anda guide.txt:

$ sed ':a; /Network$/{$!{N;ba}}; s/Network\nAdministrator/System\nUser/g; s/Network Administrator/System User/g' guide.txt 
This guide is meant to walk you through a day as a System
User. By the end, hopefully you will be better
equipped to perform your duties as a System User
and maybe even enjoy being a System User that much more.
System User
System User
I'm a System User

Kuncinya adalah terus membaca dalam antrean sampai Anda menemukan satu yang tidak berakhir dengan Network. Ketika itu selesai, pergantian bisa dilakukan.

Catatan Kompatibilitas: Semua penggunaan \ndi atas dalam teks pengganti. Ini membutuhkan sed GNU. Ini tidak akan berfungsi pada BSD / OSX sed.

[Kiat ujung ke Filipina .]

Versi multiline

Jika ini membantu mengklarifikasi, berikut ini perintah yang sama terbagi atas beberapa baris:

$ sed ':a
    /Network$/{
       $!{
           N
           ba
       }
    }
    s/Network\nAdministrator/System\nUser/g
    s/Network Administrator/System User/g
    ' filename

Bagaimana itu bekerja

  1. :a

    Ini menciptakan label a.

  2. /Network$/{ $!{N;ba} }

    Jika baris ini berakhir dengan Network, maka, jika ini bukan baris terakhir ( $!) baca dan tambahkan baris berikutnya ( N) dan cabang kembali ke label a( ba).

  3. s/Network\nAdministrator/System\nUser/g

    Lakukan penggantian dengan baris baru perantara.

  4. s/Network Administrator/System User/g

    Buat substitusi dengan perantara kosong.

Solusi yang lebih sederhana (hanya GNU)

Dengan GNU sed ( bukan BSD / OSX), kita hanya perlu satu perintah pengganti:

$ sed -zE 's/Network([[:space:]]+)Administrator/System\1User/g' test1
System
User System
User

Dan pada guide.txtfile:

$ sed -zE 's/Network([[:space:]]+)Administrator/System\1User/g' guide.txt 
This guide is meant to walk you through a day as a System
User. By the end, hopefully you will be better
equipped to perform your duties as a System User
and maybe even enjoy being a System User that much more.
System User
System User
I'm a System User

Dalam hal ini, -zberi tahu sed untuk membaca hingga karakter NUL pertama. Karena file teks tidak pernah memiliki karakter nol, ini memiliki efek membaca seluruh file sekaligus. Kami kemudian dapat melakukan penggantian tanpa khawatir kehilangan garis.

Metode ini tidak baik jika file tersebut berukuran besar (biasanya berarti gigabytes). Jika ukurannya sebesar itu, maka membacanya sekaligus dapat menyusahkan sistem RAM.

Solusi yang bekerja pada GNU dan BSD

Seperti yang disarankan oleh Phillipos , berikut ini adalah solusi portabel:

sed 'H;1h;$!d;x;s/Network\([[:space:]]\)Administrator/System\1Us‌​er/g'
John1024
sumber
1
Informasi bagus, John! Terima kasih telah menjelaskan ini dan solusi alternatif Anda sangat bagus. Yang sedang berkata, saya masih tidak mengerti mengapa solusi saya bukan solusi. Tampaknya berfungsi, tetapi dengan file test.txt Anda tidak. Mengapa solusi saya tampak berhasil, tetapi sebenarnya tidak? Terima kasih banyak atas bantuannya.
dlowrie290
1
@ dlowrie290 Solusi Anda berbunyi berpasangan. Jika Network Administratorterbagi antara baris pertama dan kedua dari pasangan itu, solusi Anda berhasil membuat substitusi. Kemudian mencetak dua baris dan membaca di pasangan berikutnya. Namun, jika baris kedua dari pasangan pertama berakhir dengan Networkdan baris pertama dari pasangan kedua dimulai dengan Administrator, kode tersebut melewatinya. Kode saya menghindari ini dengan membaca dalam baris sampai menemukan kode yang tidak berakhir Network.
John1024
2
Harap perhatikan bahwa solusi multiline pertama Anda juga bergantung pada ekstensi GNU untuk sed: \nPenggantian di dalam tidak ditentukan dalam standar. sed 'H;1h;$!d;x;s/Network\([[:space:]]\)Administrator/System\1User/g'adalah cara portabel untuk melakukannya.
Philippos
@Philippos Poin luar biasa. Jawaban diperbarui untuk menyertakan solusi portabel.
John1024
1
Terima kasih atas klarifikasi, John! Sekali lagi, hal-hal hebat dan waktu / upaya Anda sangat dihargai!
dlowrie290