Як працює awk '! A [$ 0] ++'?


40

Цей однолінійний файл видаляє повторювані рядки з введення тексту без попереднього сортування.

Наприклад:

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

Оригінальний код, який я знайшов у мережах, читав:

awk '!_[$0]++'

Це ще більше мене здивувало, коли я сприйняв _особливе значення в awk, як в Perl, але виявилося це лише назвою масиву.

Тепер я розумію логіку, що лежить в основі одного вкладиша: кожен рядок введення використовується як ключ у хеш-масиві, таким чином, по завершенні хеш містить унікальні рядки в порядку прибуття.

Я хотів би дізнатися, як саме ця позначення трактується awk. Наприклад, що !означає знак чуб ( ) та інші елементи цього фрагмента коду.

Як це працює?


Назва вводить в оману, вона повинна становити 0 доларів (Нуль), а не $ о (о).
Архемар

2
Оскільки це хеш, він не упорядкований, тому "в порядку прибуття" насправді не правильний.
Кевін

Відповіді:


35

Подивимось,

 !a[$0]++

спочатку

 a[$0]

ми розглядаємо значення a[$0](масив aз цілим рядком введення ( $0) як ключ).

Якщо його не існує ( !заперечення в тесті буде дорівнювати істині)

 !a[$0]

ми друкуємо рядок введення $0(дія за замовчуванням).

Крім того, ми додаємо один ( ++) до a[$0], тому наступного разу !a[$0]буде оцінено як хибне.

Приємно, знайди !! Ви повинні подивитися на код гольфу!


1
Отже, суть така: вираз в одиничних лапках використовується awkяк тест для кожного рядка введення; щоразу, коли тест досягає успіху, awkвиконує дію в фігурних дужках, які при пропуску є {print}. Спасибі!
Олександр Щеблікін

3
@Archemar: Ця відповідь неправильна, див. Мою.
cuonglm

@AlexanderShcheblikin в awk, за замовчуванням дія {print $0}. Це означає, що все, що оцінено як істинне, виконає це як за замовчуванням. Так, наприклад, awk '1' fileдрукуються всі рядки, awk '$1' fileдрукуються всі ті рядки, перше поле яких не порожнє або 0 тощо.
fedorqui

6
@Gnouc У цій відповіді я не бачу жодної серйозної помилки. Якщо це саме ви маєте на увазі, приріст дійсно застосовується після обчислення значення виразу. Це правда, що збільшення відбувається перед друком, але це незначна неточність, яка не впливає на основне пояснення.
Жил "ТАК - перестань бути злим"

1
Я знайшов найкраще пояснення для новачка, щоб зрозуміти тут у кворі: qr.ae/TUIVxM
GP92

30

Ось обробка:

  • a[$0]: подивіться на значення ключа $0в асоціативному масиві a. Якщо його не існує, створіть його.

  • a[$0]++: збільшити значення a[$0], повернути старе значення як значення виразу. Якщо a[$0]його немає, повертайте 0та збільшуйте a[$0]значення 1( ++оператор повертає числове значення).

  • !a[$0]++: заперечує значення виразу. Якщо a[$0]++повертається 0, весь вираз оцінюється як істинний, awkвиконується виконана за замовчуванням дія print $0. В іншому випадку весь вираз оцінюється як помилковий, причини awkнічого не роблять.

Список літератури:

З gawk, ми можемо використовувати dgawk (або awk --debugз більш новою версією) для налагодження gawkсценарію. Спочатку створіть gawkсценарій з назвою test.awk:

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

Потім запустіть:

dgawk -f test.awk

або:

gawk --debug -f test.awk

У консолі налагодження:

$ 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>

Ви можете бачити, Op_postincrementбув страчений раніше Op_not.

Ви також можете використовувати siабо stepiзамість, sабо stepщоб побачити більш чітко:

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++;

3
@Archemar: Ваша відповідь вказує, що !застосовується раніше ++.
cuonglm

6
Ця відповідь неправильна. Приріст відбувається після !обчислення результату оператора. Ви плутаєте пріоритет оператора ( !a[$0]++аналізується як !(a[$0]++)) з порядком оцінки (присвоєння нового значення a[$0]відбувається після обчислення значення виразу).
Жил 'ТАК - перестань бути злим'

5
@Gnouc Це сказано прямо у цитованому вами уривку, і якби він працював так, як ви описали, цей код не дав би бажаного ефекту. Спочатку !xобчислюється значення , де x- старе значення a[$0]. Потім a[$0]встановлюється значення 1+x.
Жил "ТАК - перестань бути злим"

7
Я вважаю, що ваш аналіз того, що робить awk, є правильним. Вибачте, якщо я вчора мав на увазі інше. Однак Ваша критика відповіді Архемара неправильна. Архемар не розуміє неправильності пріоритету, ви, плутаючи пріоритет із порядком оцінки (див. Мій попередній коментар). Якщо ви видалите будь-яку згадку про відповідь Архемара у своїй, ваша відповідь повинна бути правильною. Насправді це зосереджено на доведенні Архемара неправильним, і це не так.
Жил 'ТАК - перестань бути злим'

5
ну, принаймні зараз я знаю про налагоджувач awk ...
Archemar
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.