sed: baca seluruh file ke dalam ruang pola tanpa gagal pada input baris tunggal

9

Membaca seluruh file ke dalam ruang pola berguna untuk mengganti baris baru, & c. dan ada banyak contoh yang menyarankan hal berikut:

sed ':a;N;$!ba; [commands...]'

Namun, gagal jika input hanya berisi satu baris.

Sebagai contoh, dengan dua input baris, setiap baris dikenai perintah substitusi:

$ echo $'abc\ncat' | sed ':a;N;$!ba; s/a/xxx/g'
xxxbc
cxxxt

Tetapi, dengan input baris tunggal, tidak ada substitusi yang dilakukan:

$ echo 'abc' | sed ':a;N;$!ba; s/a/xxx/g'
abc

Bagaimana seseorang menulis sedperintah untuk membaca semua input sekaligus dan tidak memiliki masalah ini?

dicktyr
sumber
Saya mengedit pertanyaan Anda sehingga berisi pertanyaan yang sebenarnya. Anda dapat menunggu jawaban lain jika suka tetapi akhirnya menandai jawaban terbaik sebagai diterima (Lihat tombol pipa di sebelah kiri jawaban, tepat di bawah tombol panah atas-bawah).
John1024
@ John1024 Terima kasih, bagus untuk memiliki contoh. Menemukan hal semacam ini cenderung mengingatkan saya bahwa "semuanya salah" tetapi saya senang beberapa dari kita tidak menyerah. :}
dicktyr
2
Ada opsi ketiga! Gunakan sed -zopsi GNU . Jika file Anda tidak memiliki null, itu akan dibaca hingga akhir file! Ditemukan dari ini: stackoverflow.com/a/30049447/582917
CMCDragonkai

Jawaban:

13

Ada segala macam alasan mengapa membaca seluruh file ke dalam ruang pola bisa salah. Masalah logika dalam pertanyaan seputar baris terakhir adalah yang umum. Hal ini terkait dengan sedsiklus garis - ketika tidak ada lagi garis dan sedpertemuan EOF melalui - ia berhenti diproses. Dan jika Anda berada di baris terakhir dan Anda menginstruksikan seduntuk mendapatkan yang lain itu akan berhenti di sana dan tidak melakukan lagi.

Yang mengatakan, jika Anda benar-benar perlu membaca seluruh file ke dalam ruang pola, maka mungkin ada baiknya mempertimbangkan alat lain pula. Faktanya adalah, sedeponymously editor aliran - dirancang untuk bekerja garis - atau blok data yang logis - pada suatu waktu.

Ada banyak alat serupa yang lebih siap untuk menangani blok file lengkap. eddan ex, misalnya, dapat melakukan banyak hal yang seddapat dilakukan dan dengan sintaksis yang sama - dan banyak lagi selain - tetapi daripada hanya beroperasi pada aliran input sambil mentransformasikannya menjadi output seperti sedhalnya, mereka juga memelihara file cadangan sementara dalam sistem file . Pekerjaan mereka buffered ke disk sesuai kebutuhan, dan mereka tidak berhenti secara tiba-tiba di akhir file (dan cenderung lebih jarang meledak di bawah tekanan buffer) . Selain itu mereka menawarkan banyak fungsi berguna yang sedtidak - semacam itu tidak masuk akal dalam konteks aliran - seperti tanda garis, undo, bernama buffer, bergabung, dan banyak lagi.

sedKekuatan utama adalah kemampuannya untuk memproses data segera setelah membacanya - dengan cepat, efisien, dan dalam aliran. Ketika Anda menyeruput file, Anda membuangnya dan Anda cenderung mengalami kesulitan kasus tepi seperti masalah baris terakhir yang Anda sebutkan, dan buffer overruns, dan kinerja yang buruk - karena data yang diuraikannya bertambah panjang waktu pemrosesan mesin regexp saat menghitung pertandingan meningkat secara eksponensial .

Mengenai poin terakhir, omong-omong: sementara saya mengerti contoh s/a/A/gkasus sangat mungkin hanya contoh naif dan mungkin bukan skrip sebenarnya yang ingin Anda kumpulkan dalam sebuah input, Anda mungkin akan merasa perlu waktu Anda untuk membiasakan diri dengan y///. Jika Anda sering mendapati diri Anda gmenggantikan satu karakter dengan yang lain, maka yitu bisa sangat berguna bagi Anda. Ini adalah transformasi yang bertentangan dengan substitusi dan jauh lebih cepat karena tidak menyiratkan regexp. Poin terakhir ini juga dapat berguna ketika mencoba untuk melestarikan dan mengulangi //alamat kosong karena tidak memengaruhi mereka tetapi dapat dipengaruhi oleh mereka. Bagaimanapun, y/a/A/adalah cara yang lebih sederhana untuk mencapai hal yang sama - dan swap juga dimungkinkan seperti:y/aA/Aa/ yang akan menukar semua huruf besar / kecil seperti pada garis untuk satu sama lain.

Anda juga harus mencatat bahwa perilaku yang Anda uraikan sebenarnya bukan apa yang seharusnya terjadi.

Dari GNU info seddi bagian BUGS yang DILAPORKAN UMUM :

  • N perintah di baris terakhir

    • Sebagian besar versi sedkeluar tanpa mencetak apa pun ketika Nperintah dikeluarkan pada baris terakhir file. GNU sedmencetak ruang pola sebelum keluar kecuali tentu saja -nsaklar perintah telah ditentukan. Pilihan ini berdasarkan desain.

    • Sebagai contoh, perilaku sed N foo barakan tergantung pada apakah foo memiliki jumlah garis genap atau ganjil. Atau, ketika menulis skrip untuk membaca beberapa baris berikutnya mengikuti pencocokan pola, implementasi tradisional sedakan memaksa Anda untuk menulis sesuatu seperti /foo/{ $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N }bukan hanya /foo/{ N;N;N;N;N;N;N;N;N; }.

    • Dalam kasus apa pun, solusi paling sederhana adalah dengan menggunakan $d;Nskrip yang bergantung pada perilaku tradisional, atau untuk mengatur POSIXLY_CORRECTvariabel ke nilai yang tidak kosong.

The POSIXLY_CORRECTvariabel lingkungan disebutkan karena POSIX menetapkan bahwa jika sedpertemuan EOF ketika mencoba sebuah Nitu harus berhenti tanpa output, tapi versi GNU sengaja istirahat dengan standar dalam hal ini. Perhatikan juga bahwa meskipun perilaku tersebut dibenarkan di atas, anggapannya adalah bahwa kasus kesalahan adalah salah satu pengeditan aliran - tidak menyeruput seluruh file ke dalam memori.

The standar mendefinisikan N's perilaku demikian:

  • N

    • Tambahkan baris input berikutnya, kurang garis \nputusnya, ke ruang pola, menggunakan garis \ntepi tertanam untuk memisahkan bahan yang ditambahkan dari bahan asli. Perhatikan bahwa nomor baris saat ini berubah.

    • Jika tidak ada baris input berikutnya yang tersedia, Nkata kerja perintah harus bercabang ke akhir skrip dan berhenti tanpa memulai siklus baru atau menyalin ruang pola ke output standar.

Pada catatan itu, ada beberapa GNU-isme lain yang diperlihatkan dalam pertanyaan - khususnya penggunaan :label, bpeternakan, dan {tanda kurung konteks fungsi }. Sebagai aturan praktis setiap sedperintah yang menerima parameter arbitrer dipahami membatasi pada \newline dalam skrip. Jadi perintahnya ...

:arbitrary_label_name; ...
b to_arbitrary_label_name; ...
//{ do arbitrary list of commands } ...

... semuanya sangat mungkin untuk bekerja secara tidak menentu tergantung pada sedimplementasi yang membacanya. Portabl mereka harus ditulis:

...;:arbitrary_label_name
...;b to_arbitrary_label_name
//{ do arbitrary list of commands
}

Hal yang sama berlaku untuk r, w, t, a, i, dan c (dan mungkin beberapa lagi yang saya lupa pada saat ini) . Dalam hampir setiap kasus mereka mungkin juga ditulis:

sed -e :arbitrary_label_name -e b\ to_arbitary_label_name -e \
    "//{ do arbitrary list of commands" -e \}

... di mana -epernyataan eksekusi baru berdiri untuk \npembatas ewline. Jadi di mana infoteks GNU menyarankan implementasi tradisional sedakan memaksa Anda untuk melakukan :

/foo/{ $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N }

... itu seharusnya ...

/foo/{ $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N
}

... tentu saja, itu tidak benar juga. Menulis naskah dengan cara itu agak konyol. Ada banyak cara sederhana untuk melakukan hal yang sama, seperti:

printf %s\\n foo . . . . . . |
sed -ne 'H;/foo/h;x;//s/\n/&/3p;tnd
         //!g;x;$!d;:nd' -e 'l;$a\' \
     -e 'this is the last line' 

... yang mencetak:

foo
.
.
.
foo\n.\n.\n.$
.$
this is the last line

... karena tperintah est - seperti kebanyakan sedperintah - tergantung pada siklus baris untuk menyegarkan register kembali dan di sini siklus baris diizinkan untuk melakukan sebagian besar pekerjaan. Itu adalah pengorbanan lain yang Anda lakukan ketika Anda menyeruput file - siklus baris tidak menyegarkan lagi, dan begitu banyak tes akan berperilaku tidak normal.

Perintah di atas tidak mengambil risiko input yang berlebihan karena hanya melakukan beberapa tes sederhana untuk memverifikasi apa yang dibaca saat membacanya. Dengan Hlama semua baris ditambahkan ke ruang pegang, tetapi jika garis cocok dengan /foo/itu menimpa hruang lama. Buffer selanjutnya xdiubah, dan s///substitusi bersyarat dicoba jika isi buffer sesuai dengan //pola terakhir yang ditangani. Dengan kata lain, //s/\n/&/3pupaya untuk mengganti baris baru ketiga di ruang yang ditahan dengan dirinya sendiri dan mencetak hasilnya jika ruang tunggu saat ini cocok /foo/. Jika itu tEST berhasil cabang naskah ke not dlabel apus - yang melakukan look dan membungkus script.

Dalam hal kedua /foo/dan baris baru ketiga tidak dapat dicocokkan bersama dalam ruang tunggu, maka //!gakan menimpa buffer jika /foo/tidak cocok, atau, jika cocok, itu akan menimpa buffer jika \newline tidak cocok (sehingga menggantikan /foo/dengan itu sendiri) . Tes halus kecil ini menjaga buffer dari mengisi tidak perlu untuk jangka panjang tidak /foo/dan memastikan proses tetap tajam karena input tidak menumpuk. Menyusul dalam kasus tidak /foo/atau //s/\n/&/3pgagal buffer sekali lagi bertukar dan setiap baris tetapi yang terakhir ada dihapus.

Yang terakhir - baris terakhir $!d- adalah demonstrasi sederhana tentang bagaimana sedscript top-down dapat dibuat untuk menangani banyak kasus dengan mudah. Ketika metode umum Anda adalah untuk memangkas kasus-kasus yang tidak diinginkan dimulai dengan yang paling umum dan bekerja ke arah yang paling spesifik maka kasus tepi dapat lebih mudah ditangani karena mereka hanya diperbolehkan masuk ke bagian akhir skrip dengan data yang Anda inginkan lainnya dan ketika semuanya membungkus Anda dengan data yang Anda inginkan. Namun, harus mengambil case edge dari loop tertutup bisa jauh lebih sulit untuk dilakukan.

Dan inilah hal terakhir yang harus saya katakan: jika Anda harus benar-benar menarik seluruh file, maka Anda dapat melakukan sedikit pekerjaan dengan mengandalkan siklus baris untuk melakukannya untuk Anda. Biasanya Anda akan menggunakan Next dan next untuk lookahead - karena mereka maju sebelum siklus garis. Daripada menerapkan loop tertutup secara berulang dalam satu loop - karena sedsiklus hanya merupakan loop baca sederhana - jika tujuan Anda hanya untuk mengumpulkan input tanpa pandang bulu, maka mungkin lebih mudah untuk dilakukan:

sed 'H;1h;$!d;x;...'

... yang akan mengumpulkan seluruh file atau gagal mencoba.


catatan samping tentang Ndan perilaku baris terakhir ...

sementara saya tidak memiliki alat yang tersedia untuk saya uji, pertimbangkan bahwa Nketika membaca dan mengedit di tempat berperilaku berbeda jika file yang diedit adalah file skrip untuk dibaca berikutnya.

mikeserv
sumber
1
Menempatkan yang tak bersyarat Hterlebih dahulu itu menyenangkan.
sampai
@ mikeserv Terima kasih atas masukan Anda. Saya dapat melihat manfaat potensial dalam menjaga siklus siklus, tetapi bagaimana cara kerjanya lebih sedikit?
dicktyr
@dicktyr yah, sintaks mengambil beberapa pintasan :a;$!{N;ba}seperti yang saya sebutkan di atas - lebih mudah untuk menggunakan formulir standar dalam jangka panjang ketika Anda mencoba menjalankan regexps pada sistem yang tidak dikenal. Tapi itu tidak benar-benar apa yang saya maksudkan: Anda menerapkan loop tertutup - Anda tidak dapat dengan mudah masuk ke tengah-tengah itu ketika Anda ingin seperti yang Anda mungkin lakukan dengan bercabang - memangkas data yang tidak diinginkan - dan membiarkan siklus terjadi. Ini seperti hal top-down - semuanya seddilakukan adalah akibat langsung dari apa yang baru saja dilakukan. Mungkin Anda melihatnya secara berbeda - tetapi jika Anda mencobanya, Anda mungkin akan menemukan skripnya lebih mudah.
mikeserv
11

Gagal karena Nperintah datang sebelum pola cocok $!(bukan baris terakhir) dan berhenti sebelum melakukan pekerjaan apa pun:

N

Tambahkan baris baru ke ruang pola, lalu tambahkan baris input berikutnya ke ruang pola. Jika tidak ada lagi input maka sed keluar tanpa memproses perintah lagi .

Ini dapat dengan mudah diperbaiki untuk bekerja dengan input single-line juga (dan memang untuk menjadi lebih jelas dalam hal apa pun) hanya dengan mengelompokkan Ndan bperintah setelah pola:

sed ':a;$!{N;ba}; [commands...]'

Ia bekerja sebagai berikut:

  1. :a buat label bernama 'a'
  2. $! jika bukan baris terakhir, maka
  3. Ntambahkan baris berikutnya ke ruang pola (atau berhenti jika tidak ada baris berikutnya) dan bacabang (pergi ke) label 'a'

Sayangnya, ini tidak portabel (karena bergantung pada ekstensi GNU), tetapi alternatif berikut (disarankan oleh @mikeserv) adalah portabel:

sed 'H;1h;$!d;x; [commands...]'
dicktyr
sumber
Saya memposting ini di sini karena saya tidak menemukan informasi di tempat lain dan saya ingin membuatnya tersedia sehingga orang lain dapat menghindari masalah dengan meluas :a;N;$!ba;.
dicktyr
Terima kasih untuk posting! Ingatlah bahwa menerima jawaban Anda sendiri juga baik-baik saja. Anda hanya perlu menunggu beberapa saat sebelum sistem memungkinkan Anda melakukannya.
terdon