Bagaimana awk '! A [$ 0] ++' bekerja?

40

Lapisan satu ini menghapus garis duplikat dari input teks tanpa pra-sortasi.

Sebagai contoh:

$ cat >f
q
w
e
w
r
$ awk '!a[$0]++' <f
q
w
e
r
$ 

Kode asli yang saya temukan di internet berbunyi:

awk '!_[$0]++'

Ini bahkan lebih membingungkan bagi saya karena saya mengambil _arti khusus dalam awk, seperti di Perl, tetapi ternyata hanya nama array.

Sekarang, saya mengerti logika di balik one-liner: setiap baris input digunakan sebagai kunci dalam array hash, dengan demikian, setelah selesai, hash berisi garis-garis unik dalam urutan kedatangan.

Yang ingin saya pelajari adalah bagaimana tepatnya notasi ini ditafsirkan oleh awk. Misalnya apa arti tanda bang ( !) dan elemen lain dari cuplikan kode ini.

Bagaimana cara kerjanya?

Alexander Shcheblikin
sumber
judul menyesatkan, itu harus $ 0 (Nol), bukan $ o (o).
Archemar
2
Karena hash, tidak teratur, jadi "sesuai urutan kedatangan" sebenarnya tidak benar.
Kevin

Jawaban:

35

Ayo lihat,

 !a[$0]++

pertama

 a[$0]

kita melihat nilai dari a[$0](array adengan seluruh baris input ( $0) sebagai kunci).

Jika tidak ada ( !negasi dalam ujian akan mengevaluasi kebenarannya)

 !a[$0]

kami mencetak baris input $0(tindakan default).

Kami juga menambahkan satu ( ++) ke a[$0], jadi lain kali !a[$0]akan bernilai false.

Bagus, temukan !! Anda harus melihat kode golf!

Archemar
sumber
1
Jadi intinya adalah ini: ekspresi dalam tanda kutip tunggal digunakan oleh awksebagai tes untuk setiap baris input; setiap kali tes berhasil awkmengeksekusi aksi dalam kurung kurawal, yang bila dihilangkan adalah {print}. Terima kasih!
Alexander Shcheblikin
3
@Archemar: Jawaban ini salah, lihat milikku.
cuonglm
@AlexanderShcheblikin awk, tindakan defaultnya adalah {print $0}. Ini berarti bahwa apa pun yang dievaluasi sebagai true akan menjalankan ini sebagai default. Jadi misalnya awk '1' filemencetak semua baris, awk '$1' filemencetak semua baris yang bidang pertama tidak kosong atau 0, dll.
fedorqui
6
@ Gnouc Saya tidak melihat ada kesalahan serius dalam jawaban ini. Jika itu yang Anda maksudkan, kenaikan memang diterapkan setelah nilai ekspresi dihitung. Memang benar bahwa peningkatan terjadi sebelum pencetakan, tetapi itu adalah ketidaktepatan kecil yang tidak mempengaruhi penjelasan dasar.
Gilles 'SO- stop being evil'
1
Saya menemukan penjelasan terbaik untuk dipahami oleh seorang pemula di sini di quora: qr.ae/TUIVxM
GP92
30

Berikut ini prosesnya:

  • a[$0]: lihat nilai kunci $0, dalam array asosiatif a. Jika tidak ada, buatlah.

  • a[$0]++: increment nilai a[$0], kembalikan nilai lama sebagai nilai ekspresi. Jika a[$0]tidak ada, kembali 0dan naik a[$0]ke 1( ++operator mengembalikan nilai numerik).

  • !a[$0]++: meniadakan nilai ekspresi. Jika a[$0]++kembali 0, seluruh ekspresi dievaluasi ke true, lakukan awktindakan default yang dilakukan print $0. Kalau tidak, seluruh ekspresi dievaluasi menjadi false, menyebabkan awktidak melakukan apa pun.

Referensi:

Dengan gawk, kita dapat menggunakan dgawk (atau awk --debugdengan versi yang lebih baru) untuk men-debug gawkskrip. Pertama, buat gawkskrip, beri nama test.awk:

BEGIN {                                                                         
    a = 0;                                                                      
    !a++;                                                                       
}

Lalu lari:

dgawk -f test.awk

atau:

gawk --debug -f test.awk

Di konsol debugger:

$ dgawk -f test.awk
dgawk> trace on
dgawk> watch a
Watchpoint 1: a
dgawk> run
Starting program: 
[     1:0x7fe59154cfe0] Op_rule             : [in_rule = BEGIN] [source_file = test.awk]
[     2:0x7fe59154bf80] Op_push_i           : 0 [PERM|NUMCUR|NUMBER]
[     2:0x7fe59154bf20] Op_store_var        : a [do_reference = FALSE]
[     3:0x7fe59154bf60] Op_push_lhs         : a [do_reference = TRUE]
Stopping in BEGIN ...
Watchpoint 1: a
  Old value: untyped variable
  New value: 0
main() at `test.awk':3
3           !a++;
dgawk> step
[     3:0x7fe59154bfc0] Op_postincrement    : 
[     3:0x7fe59154bf40] Op_not              : 
Watchpoint 1: a
  Old value: 0
  New value: 1
main() at `test.awk':3
3           !a++;
dgawk>

Anda bisa lihat, Op_postincrementsudah dieksekusi sebelumnya Op_not.

Anda juga dapat menggunakan siatau stepibukannya satau stepuntuk melihat lebih jelas:

dgawk> si
[     3:0x7ff061ac1fc0] Op_postincrement    : 
3           !a++;
dgawk> si
[     3:0x7ff061ac1f40] Op_not              : 
Watchpoint 1: a
  Old value: 0
  New value: 1
main() at `test.awk':3
3           !a++;
cuonglm
sumber
3
@Archemar: Jawaban Anda menunjukkan bahwa !sudah diterapkan sebelumnya ++.
cuonglm
6
Jawaban ini salah. Peningkatan terjadi setelah hasil !operator dihitung. Anda membingungkan prioritas operator ( !a[$0]++diurai seperti !(a[$0]++)) dengan urutan evaluasi (penugasan nilai baru a[$0]terjadi setelah nilai ekspresi dihitung).
Gilles 'SO- stop being evil'
5
@ Gnouc Dikatakan tepat di bagian yang Anda kutip, dan jika itu bekerja seperti yang Anda jelaskan, kode ini tidak akan memiliki efek yang diinginkan. Pertama nilai !xdihitung, di mana xnilai lama a[$0]. Kemudian a[$0]diatur ke 1+x.
Gilles 'SO- stop being evil'
7
Saya percaya bahwa analisis Anda tentang apa yang dilakukan awk adalah benar. Maaf jika saya menyiratkan sebaliknya kemarin. Namun, kritik Anda terhadap jawaban Archemar salah. Archemar tidak salah memahami prioritas, Anda tahu, Anda membingungkan prioritas dengan urutan evaluasi (lihat komentar saya sebelumnya). Jika Anda menghapus penyebutan jawaban Archemar di dalam milik Anda, jawaban Anda seharusnya benar. Seperti itu, ini difokuskan untuk membuktikan Archemar salah, dan ini bukan masalahnya.
Gilles 'SO- stop being evil'
5
baik, setidaknya sekarang saya tahu tentang debugger awk ...
Archemar