Mode slurp dalam awk?


16

Alat seperti sed, awkatau perl -nmemproses input mereka satu rekaman pada satu waktu, catatan menjadi garis secara default.

Beberapa, seperti awkdengan RS, GNU seddengan -zatau perldengan -0ooodapat mengubah jenis catatan dengan memilih pemisah rekaman yang berbeda.

perl -ndapat membuat seluruh input (setiap file individu ketika melewati beberapa file) satu catatan dengan -0777opsi (atau -0diikuti oleh angka oktal lebih besar dari 0377, 777 menjadi yang kanonik). Itulah yang mereka sebut mode slurp .

Dapat sesuatu yang mirip dilakukan dengan awk's RSatau mekanisme lain? Di mana awkmemproses setiap konten file secara keseluruhan agar bertentangan dengan setiap baris dari setiap file?

Jawaban:


15

Anda dapat mengambil pendekatan berbeda tergantung pada apakah awkmemperlakukan RSsebagai karakter tunggal (seperti awkimplementasi tradisional lakukan) atau sebagai ekspresi reguler (suka gawkatau mawktidak). File kosong juga rumit untuk dipertimbangkan karena awkcenderung melompati mereka.

gawk, mawkatau awkimplementasi lain di mana RSbisa menjadi regexp.

Dalam implementasi tersebut (untuk mawk, berhati-hatilah bahwa beberapa OS seperti Debian mengirimkan versi yang sangat lama dan bukan versi modern yang dikelola oleh @ThomasDickey ), jika RSberisi satu karakter, pemisah rekaman adalah karakter itu, atau awkmemasuki mode paragraf ketika RSkosong, atau memperlakukan RSsebagai ekspresi reguler jika tidak.

Solusinya adalah menggunakan ekspresi reguler yang tidak mungkin dapat dicocokkan. Beberapa muncul di pikiran seperti x^atau $x( xsebelum memulai, atau setelah akhir). Namun beberapa (terutama dengan gawk) lebih mahal daripada yang lain. Sejauh ini, saya telah menemukan itu ^$yang paling efisien. Itu hanya bisa cocok dengan input kosong, tetapi kemudian tidak akan ada yang cocok dengan.

Jadi kita bisa melakukan:

awk -v RS='^$' '{printf "%s: <%s>\n", FILENAME, $0}' file1 file2...

Satu peringatan adalah bahwa ia melewatkan file kosong (bertentangan dengan perl -0777 -n). Itu bisa diatasi dengan GNU awkdengan memasukkan kode dalam ENDFILEpernyataan sebagai gantinya. Tetapi kita juga perlu mengatur ulang $0dalam pernyataan BEGINFILE karena jika tidak maka tidak akan diatur ulang setelah memproses file kosong:

gawk -v RS='^$' '
   BEGINFILE{$0 = ""}
   ENDFILE{printf "%s: <%s>\n", FILENAME, $0}' file1 file2...

awkimplementasi tradisional , POSIXawk

Pada mereka, RShanya satu karakter, mereka tidak memiliki BEGINFILE/ ENDFILE, mereka tidak memiliki RTvariabel, mereka juga umumnya tidak dapat memproses karakter NUL.

Anda akan berpikir bahwa menggunakan RS='\0'bisa bekerja maka karena bagaimanapun mereka tidak dapat memproses input yang berisi byte NUL, tetapi tidak, yang RS='\0'dalam implementasi tradisional diperlakukan sebagai RS=, yang merupakan mode paragraf.

Salah satu solusinya adalah menggunakan karakter yang tidak mungkin ditemukan di input seperti \1. Di lokal karakter multibyte, Anda bahkan dapat membuatnya byte-sequence yang sangat tidak mungkin terjadi karena mereka membentuk karakter yang tidak ditugaskan atau non-karakter seperti $'\U10FFFE'di lokal UTF-8. Tidak terlalu mudah dan Anda memiliki masalah dengan file kosong juga.

Solusi lain dapat menyimpan seluruh input dalam suatu variabel dan memprosesnya dalam pernyataan AKHIR di bagian akhir. Itu berarti Anda hanya dapat memproses satu file pada satu waktu:

awk '{content = content $0 RS}
     END{$0 = content
       printf "%s: <%s>\n", FILENAME, $0
     }' file

Itu setara seddengan:

sed '
  :1
  $!{
   N;b1
  }
  ...' file1

Masalah lain dengan pendekatan itu adalah bahwa jika file tidak berakhir dengan karakter baris baru (dan tidak kosong), orang masih secara sewenang-wenang ditambahkan di $0akhir (dengan gawk, Anda akan mengatasinya dengan menggunakan RTalih-alih RSdi kode di atas). Satu keuntungan adalah bahwa Anda memiliki catatan jumlah baris dalam file di NR/ FNR.


seperti untuk bagian terakhir ("jika file tidak diakhiri dengan karakter baris baru (dan tidak kosong), orang masih ditambahkan sewenang-wenang dalam $ 0 di akhir"): untuk file teks, mereka seharusnya memiliki akhiran garis baru. vi menambahkan satu, misalnya, dan dengan demikian memodifikasi file ketika Anda menyimpannya. Tidak memiliki baris baru terminasi membuat beberapa perintah membuang "baris" terakhir (mis: wc) tetapi yang lain masih 'melihat' baris terakhir ... ymmv. Solusi Anda karena itu valid, imo, jika Anda seharusnya memperlakukan file teks (yang mungkin terjadi, karena awk baik untuk pemrosesan teks tetapi tidak begitu baik untuk binari ^^)
Olivier Dulac

1
mencoba untuk menghirup semuanya mungkin mengenai beberapa batasan ... awak tradisional tampaknya memiliki (memiliki?) batas 99 bidang pada satu baris ... jadi Anda mungkin perlu menggunakan FS yang berbeda juga untuk menghindari batas itu, tetapi Anda mungkin juga memiliki batasan berapa lama total garis (atau semuanya, jika Anda berhasil mendapatkan semuanya dalam satu baris)?
Olivier Dulac

akhirnya: hack (konyol ...) bisa menjadi parse ke-1 seluruh file dan mencari char yang tidak ada di sana, lalu tr '\n' 'thatchar' file sebelum mengirimnya untuk awk, dan tr 'thatchar' \n'hasilnya? (Anda mungkin masih harus menambahkan baris baru untuk memastikan, seperti yang saya sebutkan di atas, file input Anda memiliki baris baru yang berhenti: { tr '\n' 'missingchar' < thefile ; printf "\n" ;} | awk ..... | { tr 'missingchar' '\n' }(tetapi itu menambahkan '\ n' pada akhirnya, bahwa Anda mungkin harus menyingkirkan ... mungkin menambahkan sed sebelum tr akhir? jika tr menerima file tanpa menghentikan baris baru ...)
Olivier Dulac

@OlivierDulac, batas jumlah bidang hanya akan dipukul jika kami mengakses NF atau bidang apa pun. awktidak melakukan pemisahan jika kita tidak. Karena itu, bahkan /bin/awkSolaris 9 (berdasarkan awk tahun 1970-an) tidak memiliki batasan itu, jadi saya tidak yakin kita dapat menemukan yang melakukannya (masih mungkin karena elawk SVR4 memiliki batas 99 dan nawk 199, jadi itu adalah kemungkinan pencabutan batas itu ditambahkan oleh Sun dan mungkin tidak ditemukan dalam awks berbasis SVR4 lainnya, dapatkah Anda menguji pada AIX?).
Stéphane Chazelas
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.