Как работает 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 (ноль), а не $ o (o).
Архемар
2
Поскольку это хеш, он неупорядочен, поэтому «в порядке поступления» на самом деле не правильно.
Кевин

Ответы:

35

Посмотрим,

 !a[$0]++

первый

 a[$0]

мы смотрим на значение a[$0](массив aс целой строкой ввода ( $0) в качестве ключа).

Если он не существует ( !отрицание в тесте будет равно true)

 !a[$0]

мы печатаем строку ввода $0(действие по умолчанию).

Кроме того, мы добавляем one ( ++) к a[$0], так что в следующий раз мы !a[$0]получим значение false.

Здорово, найди !! Вы должны взглянуть на код гольф!

Archemar
источник
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, все выражение оценивается как true, выполнить 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++;
cuonglm
источник
3
@Archemar: Ваш ответ указывает, что !применяется раньше ++.
cuonglm
6
Этот ответ неверен. Увеличение происходит после !вычисления результата оператора. Вы путаете приоритет оператора ( !a[$0]++аналогично синтаксическому анализу !(a[$0]++)) с порядком вычисления (присвоение нового значения a[$0]происходит после вычисления значения выражения).
Жиль "ТАК - перестань быть злым"
5
@Gnouc В самом отрывке, который вы цитировали, написано, и если бы он работал так, как вы описали, этот код не дал бы желаемого эффекта. Сначала !xвычисляется значение , где xнаходится старое значение a[$0]. Затем a[$0]устанавливается на 1+x.
Жиль "ТАК - перестань быть злым"
7
Я считаю, что ваш анализ того, что делает awk, верен. Извините, если я подразумевал иначе вчера. Однако ваша критика ответа Архемара неверна. Archemar не неправильно понимает приоритет, вы понимаете, вы путаете приоритет с порядком оценки (см. Мой предыдущий комментарий). Если вы удалите упоминание об ответе Archemar в вашем, ваш ответ должен быть правильным. Как таковой, он сосредоточен на доказательстве Archemar неправильно, и это не так.
Жиль "ТАК ... перестать быть злым"
5
ну, по крайней мере, теперь я знаю об отладчике awk ...
Archemar