Mengapa awk berhenti dan menunggu jika nama file mengandung = dan bagaimana cara mengatasinya?

25
awk 'processing_script_here' my=file.txt

tampaknya berhenti dan menunggu tanpa batas waktu ...
Apa yang terjadi di sini dan bagaimana cara membuatnya berfungsi?

don_crissti
sumber

Jawaban:

19

Seperti kata Chris , argumen formulir variablename=anythingdiperlakukan sebagai penugasan variabel (yang dilakukan pada saat argumen diproses sebagai lawan dari yang (lebih baru) -v var=valueyang dilakukan sebelum BEGINpernyataan) alih-alih memasukkan nama file input.

Itu bisa berguna dalam hal-hal seperti:

awk '{print $1}' FS=/ RS='\n' file1 FS='\n' RS= file2

Di mana Anda dapat menentukan file yang berbeda FS/ RSper. Ini juga biasa digunakan di:

awk '!file1_processed{a[$0]; next}; {...}' file1 file1_processed=1 file2

Yang merupakan versi lebih aman dari:

awk 'NR==FNR{a[$0]; next}; {...}' file1 file2

(yang tidak berfungsi jika file1kosong)

Tapi itu menghalangi ketika Anda memiliki file yang namanya berisi =karakter.

Sekarang, itu hanya masalah ketika apa yang tersisa dari yang pertama =adalah awknama variabel yang valid .

Apa yang merupakan nama variabel yang valid di awklebih ketat daripada di sh.

POSIX membutuhkannya seperti:

[_a-zA-Z][_a-zA-Z0-9]*

Dengan hanya karakter set karakter portabel. Namun, /usr/xpg4/bin/awkSolaris 11 setidaknya tidak sesuai dalam hal itu dan memungkinkan setiap karakter alfabet di lokal dalam nama variabel, bukan hanya a-zA-Z.

Jadi argumen seperti x+y=fooatau =baratau ./foo=barmasih diperlakukan sebagai nama file input dan bukan tugas sebagai apa yang tersisa dari yang pertama =bukan nama variabel yang valid. Argumen suka Stéphane=Chazelas.txtmungkin atau tidak mungkin, tergantung pada awkimplementasi dan lokal.

Itu sebabnya dengan awk, disarankan untuk menggunakan:

awk '...' ./*.txt

dari pada

awk '...' *.txt

misalnya untuk menghindari masalah jika Anda tidak dapat menjamin nama txtfile tidak akan mengandung =karakter.

Juga, berhati-hatilah karena argumen seperti -vfoo=bar.txtdapat dianggap sebagai opsi jika Anda menggunakan:

awk -f file.awk -vfoo=bar.txt

(juga berlaku untuk awk '{code}' -vfoo=bar.txtdengan awkdari versi busybox sebelum 1.28.0, lihat laporan bug yang sesuai ).

Sekali lagi, menggunakan ./*.txtkarya di sekitar itu (menggunakan ./awalan juga membantu dengan file yang disebut -yang sebaliknya awkdipahami sebagai input standar berarti ).

Itu juga sebabnya

#! /usr/bin/awk -f

Shebang tidak bekerja. Sementara var=valueyang bisa dikerjakan dengan memperbaikiARGV nilai - nilai (tambahkan ./awalan) dalam sebuah BEGINpernyataan:

#! /usr/bin/awk -f
BEGIN {
  for (i = 1; i < ARGC; i++)
    if (ARGV[i] ~ /^[_[:alpha:]][_[:alnum:]]*=/)
      ARGV[i] = "./" ARGV[i]
}
# rest of awk script

Itu tidak akan membantu dengan yang opsi karena yang dilihat oleh awkdan bukan awkskrip.

Salah satu masalah kosmetik potensial dengan menggunakan ./awalan itu adalah akhirnya FILENAME, tetapi Anda selalu dapat menggunakannya substr(FILENAME, 3)untuk menghapusnya jika Anda tidak menginginkannya.

Implementasi GNU awkmemperbaiki semua masalah tersebut dengan -Eopsinya.

Setelah -E, gawk hanya mengharapkan path dari awkskrip (di mana -masih berarti stdin) dan kemudian daftar path file input saja (dan di sana, bahkan tidak -diperlakukan secara khusus).

Ini dirancang khusus untuk:

#! /usr/bin/gawk -E

shebang di mana daftar argumen selalu memasukkan file (perhatikan bahwa Anda masih dapat mengedit ARGVdaftar itu dalam sebuah BEGINpernyataan).

Anda juga dapat menggunakannya sebagai:

gawk -e '...awk code here...' -E /dev/null *.txt

Kami menggunakan -Edengan skrip kosong ( /dev/null) hanya untuk memastikan bahwa *.txtsetelah itu selalu diperlakukan sebagai file input, meskipun mengandung =karakter.

Stéphane Chazelas
sumber
Saya tidak melihat bagaimana jalur eksplisit yang berakhir di FILENAME merupakan masalah. Entah skrip awk bersifat umum, dalam hal ini skrip harus menangani semua jenis jalur yang berakhir di FILENAME (termasuk tetapi tidak terbatas pada ../foo, /path/to/foodan jalur yang ada dalam penyandian berbeda) - dalam hal substr(FILENAME,3)ini tidak akan cukup, atau itu skrip satu tembakan di mana pengguna pada dasarnya tahu apa nama file - dalam hal ini / dia mungkin tidak perlu repot-repot dengan salah satu dari mereka yang mengandung =salah satu ;-)
mosvy
2
@ Mosvy Saya tidak berpikir itu menyatakan begitu banyak yang ./merupakan masalah, tetapi itu mungkin tidak diinginkan dalam kondisi tertentu, seperti kasus di mana nama file harus dimasukkan dalam output, dalam hal ini ./harus berlebihan dan tidak perlu, sehingga Anda Akan perlu untuk menyingkirkannya entah bagaimana. Setidaknya ada satu contoh . Adapun pengguna mengetahui apa nama file - baik, dalam hal ini kita juga tahu apa nama file, tetapi =masih menghalangi proses pengolahan yang tepat. Jadi bisa memimpin -menghalangi.
Sergiy Kolodyazhnyy
@ Mosvy, ya idenya adalah bahwa Anda ingin menggunakan ./awalan untuk mengatasi awkfitur (mis) itu, tetapi kemudian Anda berakhir dengan yang ./pada keluaran yang mungkin ingin Anda hapus. Lihat cara memeriksa apakah baris pertama file berisi string tertentu? sebagai contoh.
Stéphane Chazelas
Bukan hanya lokal (relatif terhadap direktori ini) ./tetapi juga global (jalur absolut) /yang membuat awk menafsirkan argumen sebagai file.
Isaac
21

Dalam sebagian besar versi awk, argumen setelah program untuk dieksekusi adalah:

  1. File
  2. Penugasan formulir x=y

Karena nama file Anda ditafsirkan sebagai kasus # 2, awk masih menunggu sesuatu untuk dibaca di stdin (karena tidak merasa bahwa ada nama file yang dilewati).

Mudahnya, perilaku ini didokumentasikan dalam POSIX :

Salah satu dari dua jenis argumen berikut ini dapat dicampur:

  • file: Nama path dari file yang berisi input untuk dibaca, yang cocok dengan set pola dalam program. Jika tidak ada operan file yang ditentukan, atau jika operan file adalah '-', input standar harus digunakan.
  • tugas: Suatu operan yang dimulai dengan karakter garis bawah atau alfabet dari set karakter portabel (lihat tabel dalam volume Definisi Basis IEEE Std 1003.1-2001, Bagian 6.1, Set Karakter Portabel), diikuti oleh urutan garis bawah, angka, dan alfabet dari set karakter portabel, diikuti oleh karakter '=', harus menentukan tugas variabel daripada nama path.

Dengan demikian, mudah dibawa, Anda memiliki beberapa opsi (# 1 kemungkinan adalah yang paling tidak mengganggu):

  1. Gunakan awk ... ./my=file, yang menghindari ini karena .bukan "karakter garis bawah atau alfabet dari set karakter portabel".
  2. Letakkan file di stdin menggunakan awk ... < my=file. Namun, ini tidak berfungsi dengan baik pada banyak file.
  3. Buat hardlink ke file sementara, dan gunakan itu. Anda dapat melakukan sesuatu seperti ln my=file my_file, dan kemudian gunakan my_fileseperti biasa. Tidak ada penyalinan yang akan dilakukan, dan kedua file akan didukung oleh data dan metadata inode yang sama. Setelah menggunakannya, aman untuk menghapus tautan yang dibuat karena jumlah referensi ke inode akan tetap lebih besar dari 0.
Chris Down
sumber
6
Tidak ./my=file bekerja % awk 'processing_script_here' ./my=file.txt awk: fatal: cannot open file ./my=file.txt' for reading (No such file or directory). Ini harus portabel karena ./mybukan nama variabel yang valid, jadi tidak boleh diuraikan seperti itu.
Stephen Harris
2
Seperti yang dikatakan teks POSIX, masalahnya hanya ketika yang pertama =didahului oleh karakter garis bawah atau alfabet dari set karakter portabel (lihat tabel dalam volume Definisi Dasar IEEE Std 1003.1-2001, Bagian 6.1, Set Karakter Portable), diikuti oleh urutan garis bawah, angka, dan alfabet dari set karakter portabel . jadi path file seperti ++foo=bar.txtatau =fooatau ./foo=barsemuanya OK karena itu .atau +bukan [_a-zA-Z].
Stéphane Chazelas
1
@SergiyKolodyazhnyy awk adalah eksternal ke shell, jadi tidak masalah yang Anda gunakan. ./my=fileakan melewati kata demi kata.
Chris Down
1
@SergiyKolodyazhnyy, sama untuk awk '{print $1,$2}' /etc/passwd. Intinya adalah bahwa memiliki shell membuka file sebagai lawan awk tidak membuat perbedaan, apakah itu membuatnya dapat dicari atau tidak. Sebenarnya, dalam awk '{exit}' < /etc/passwd, Anda akan berharap awkuntuk mencari kembali ke akhir catatan pertama setelah itu exituntuk memastikan itu meninggalkan posisi dalam stdin di sana. POSIX mensyaratkan itu. /usr/xpg4/bin/awkmelakukannya di Solaris, tetapi tampaknya tidak gawkjuga mawkmelakukannya di GNU / Linux.
Stéphane Chazelas
3
@mosvy, lihat bagian INPUT FILES di pubs.opengroup.org/onlinepubs/9699919799/utilities/... Berguna dalam sejumlah pola penggunaan yang hanya masuk akal dengan file biasa seperti ketika Anda ingin memotong file atau menulis data ke dalamnya di posisi yang diidentifikasi dengan awkcara itu.
Stéphane Chazelas
3

Mengutip dokumentasi gawk (penekanan catatan ditambahkan):

Setiap argumen tambahan pada baris perintah biasanya diperlakukan sebagai file input untuk diproses dalam urutan yang ditentukan. Namun, argumen yang memiliki bentuk var = nilai, memberikan nilai nilai ke variabel var - itu tidak menentukan file sama sekali.

Mengapa perintah itu berhenti dan menunggu? Karena dalam bentuk awk 'processing_script_here' my=file.txt tidak ada file yang ditentukan oleh definisi di atas - my=file.txtditafsirkan sebagai penugasan variabel, dan jika tidak ada file yang didefinisikan awkakan membaca stdin (juga jelas dari straceyang menunjukkan bahwa awk dalam perintah tersebut sedang menungguread(0,'...) syscall.

Ini juga didokumentasikan dalam spesifikasi POSIX awk , lihat bagian dan tugas operan bagian dari itu)

Tugas variabel jelas dalam awk '{print foo}' foo=bar /etc/passwdnilai foodicetak untuk setiap baris di / etc / passwd. Menentukan./foo=bar atau path lengkap tidak berfungsi.

Perhatikan bahwa berjalan stracepada awk '1' foo=barserta memeriksa dengancat foo=bar menunjukkan bahwa ini adalah masalah awk spesifik, dan execve melakukan acara nama file sebagai argumen berlalu, sehingga kerang tidak ada hubungannya dengan tugas variabel env dalam kasus ini.

Selain itu, harap dicatat bahwa awk '...script...' foo=bartidak akan menyebabkan pembuatan variabel lingkungan oleh shell, karena tugas variabel lingkungan harus mendahului perintah untuk berlaku. Lihat POSIX Shell Grammar Rules , poin nomor 7. Selain itu, ini dapat diverifikasi melaluiawk '{print ENVIRON["foo"]}' foo=bar /etc/passwd

Sergiy Kolodyazhnyy
sumber