Как работает awk '! A [$ 0] ++'?

ага, поэтому мою проблему можно решить следующим образом:

ssh host "mkdir -p /target"
scp tmp/24/* host:/target
41
06.10.2014, 23:56
2 ответа

Результат

Во-первых, окончательный результат.:-)

Какое бы выражение ни было написано, единственное, что нужно знать awk, — это trueили false. Значение 0или пустая строка понимается как false. А любой другой результат понимается как true. Так:

awk 'expression' file

На самом деле это сокращение от:

awk 'expression {print $0}' file

Если результат выражения истинен (для определенной строки ), эта строка будет напечатана. В противном случае ничего не печатается.
Численно:1печатает, 0нет.

Что нам нужно понять, так это то, что является конечным результатом выражения и как мы его получаем.

Массив

Последовательность обработки выражения !a[$0]++должна начинаться с самой внутренней части выражения, подобно обработке скобок, мы должны начинать с самой внутренней пары из них, где все известно.

Единственное, что мы знаем, это значение $0, значение строки, обрабатываемой awk. Это используется в ассоциативном массиве a[$0]. Ассоциативный, потому что индексы могут быть текстовыми, а не только числовыми. Ссылка на массив по некоторому ключу (, например a[one], если строкаone)создает экземпляр (, создает )пространство массива в памяти для его хранения.Он также инициализируется пустой строкой (, эквивалентной a[one]=""). Если в будущем появится еще одна строка со значениемone(того же ключа ), новая память не используется, так как позиция массива уже создана.

Для каждой уникальной строки во входном файле эта строка сохраняется как значение индекса в памяти. Если имеется много уникальных строк, может использоваться много памяти. Но это компромисс -отсутствия необходимости сортировки/уникализации входного файла.

На этом этапе нам нужно решить, как следует обрабатывать !x++.

++

Этот шаг вызвал много споров в предыдущих постах.
Обсуждалось, будет ли следующей операцией ++или !. И если результат ++следует использовать для вычисления !.

Я постараюсь сделать это предельно ясно.

Из таблицы приоритетов операторов видно, что на третьем уровне находятся ++и --и чуть ниже, на 5-м уровне, +, -и !.

Так же, как 2+3*4необходимо выполнить в порядке старшинства :сначала умножить 3на 4и к результату этого выражения прибавить 2. Почему сначала умножить? Потому что он выше в таблице приоритетов, а затем добавить как есть на самый низкий (последний )уровень.

Значение ++выше, чем !, и его необходимо применять первым. Нет никакого способа обойти это ИМО.

Тем не менее, ++имеют внутренний поворот. На самом деле он генерирует два результата. Одним из них является значение переменной (, технически это lvalue ), которое увеличивается. Это заканчивается сохраненным в ячейке памяти. Второй - это значение, которое дается как результат выражения, оцененного до этой точки.Это дается следующему элементу выражения:

  1. Значение переменной (lvalue )увеличивается и сохраняется в своем месте в памяти точно так же, независимо от того, является ли оно приращением до -++xили приращением после -x++. ]. Это не имеет значения.

  2. Но значение, данное как результат выражения x++или ++x, отличается. В одном:x++результатом выражения и значением, используемым для следующего оператора в написанном выражении, является до увеличения . Или, говоря наоборот (, но не обязательно последовательность того, что происходит ), равна :значение увеличивается после его использования в выражении. С ++xпоследовательность и объяснение обратные.

Причина, по которой точная последовательность не может быть точно определена, заключается в том, что в большинстве реализаций порядок действий:

  1. Получить значение переменной (lvalue )и поместить его в регистр (предположим A ).
  2. Применить оператор ++, который, как я объяснял выше, должен привести к двум значениям (на пост -приращение):
  • Скопируйте значение из регистра A в регистр B.
  • Увеличение регистра B.

Два два значения являются результатами в регистрах A и B.

  1. Некоторые языки сразу же сохраняют значение в регистре B в памяти, И:

  2. Продолжить с выражением, используя значение в регистре A.

  3. Некоторые языки ждут, пока все части выражения не будут вычислены (в надежде, что значение в регистре B снова используется где-то в выражении и его не нужно снова извлекать из памяти )и , затем возвращают значение в регистре B в память.

Но сейчас должно быть ясно, что ++применяется первым.

!

Затем к не -приращенному значению (для поста -приращение )мы применяем !.
Если значение a[$0]было нулевым, (начиналось как пустое, )оно инвертируется и генерирует 1. С результатом 1строка будет напечатана.

Если элемент массива a[$0]уже был замечен, значение, хранящееся в этой позиции массива, является результатом приращения предыдущего поста -, поэтому оно является числовым и больше, чем 0. В первый раз нуль (, эквивалентный пустой строке ), увеличивается до 1, и это то, что содержит a[$0]. В последовательных строках он может быть увеличен до больших чисел (, если только он не переполнит ), но он не вернется к 0. !не любое число (, кроме нуля ), равно нулю, и строка не будет напечатана.

-2
11.07.2020, 11:44

ах, вездесущий, но зловещий инструмент для удаления дубликатов awk

awk '!a[$0]++'

этот сладкий ребенок - дитя любви силы и лаконичности awk. вершина awk one лайнеров. короткий, но мощный и загадочный одновременно. удаляет дубликаты, сохраняя порядок. подвиг, недостигнутый uniqили sort -u, который либо удаляет только соседние дубликаты, либо должен нарушить порядок, чтобы удалить дубликаты.

вот моя попытка объяснить, как работает этот awk one liner. я приложил усилия, чтобы объяснить вещи, чтобы кто-то, кто не знает ни одного awk, мог все еще следовать. я надеюсь, что я был в состоянии сделать это.

сначала некоторый псевдокод. то, что делает этот лайнер, в основном следующее:

for every line:
  if i have not seen this line before:
    print line
  take note that i have now seen this line

Я надеюсь, вы видите, как это удаляет дубликаты, сохраняя порядок.

Но как цикл, оператор if, печать и механизм хранения и извлечения строк умещаются в 8 символах awk-кода? ответ неявный.

цикл является неявным.

Один из основных принципов разработки awk заключается в том, что он выполняет неявный цикл для каждой строки ввода. большая часть кода в awk, который вы пишете, находится внутри этого неявного цикла.

печать неявна

это

awk '!a[$0]++'

эквивалентно этому

awk '!a[$0]++ { print $0 }'

$0— переменная awk для текущей строки. print $0означает печать текущей строки.

если подразумевается

эта вещь !a[$0]++ { print $0 }является "правилом" awk. правило состоит из условия и блока кода. это !a[$0]++является условием, а это { print $0 }является кодовым блоком.

типичная программа awk состоит из одного или нескольких правил. для каждой входной строки awk проверяет условие и, если оно истинно, выполняет блок кода. если условие отсутствует, то оно неявно верно. если блок кода отсутствует, то он неявный { print $0 }.

так что эта штука !a[$0]++каким-то образом оценивается либо как истина, либо как ложь. если true, строка печатается. если ложь нет.

давайте еще раз посмотрим на псевдокод

for every line:                            # implicit by awk
  if i have not seen this line before:     # at least we know the boolean part
    print line                             # implicit by awk
  take note that i have now seen this line # ???

мы понимаем цикл, печать и если. но как это работает, чтобы оно оценивалось как false только в повторяющихся строках? и как он отмечает уже увиденные линии?

давайте разберем этого зверя:!a[$0]++

если вы знаете c или java, вы должны уже знать некоторые символы. семантика идентична или, по крайней мере, похожа.

восклицательный знак(!)отрицатель. он оценивает выражение как логическое значение и отрицает любой результат. если выражение оценивается как истинное, конечный результат является ложным, и наоборот.

a[..]— это массив. ассоциативный ряд. другие языки называют это картой или словарем. в awk все массивы являются ассоциативными массивами. aне имеет особого значения. это просто имя массива. с таким же успехом это может быть xили eliminatetheduplicate.

$0— текущая строка ввода. это специфическая переменная awk.

плюс плюс(++)— это оператор постинкремента. этот оператор немного сложен, потому что он делает две вещи: :значение в переменной увеличивается. но значение, «возвращенное» для дальнейшей обработки, является исходным значением.

   !        a[         $0       ]        ++
negator   array   current line      post increment

как они работают вместе?

примерно в таком порядке:

  1. $0— текущая строка
  2. a[$0]— значение в массиве для текущей строки
  3. приращение поста(++)получает значение из a[$0]; увеличивает и сохраняет его обратно в a[$0]; затем «возвращает» исходное значение следующему оператору в строке, который является отрицателем.
  4. инвертор(!)получает значение из ++, которое было исходным значением из a[$0]; он оценивается как логическое значение, затем инвертируется, а затем передается в if. if then решает, печатать строку или нет.

, так что это означает, будет ли строка напечатана или нет, или, другими словами, :является ли строка дубликатом или нет, в конечном итоге определяется значением в a[$0].

расширение, примечание о том, что эта строка уже была видна, должно произойти, когда ++сохраняет увеличенное значение обратно вa[$0]

давайте еще раз посмотрим на псевдокод

for every line:
  if i have not seen this line before:     # read from a[$0]
    print line
  take note that i have now seen this line # write to a[$0]

но если мы попытаемся поместить эту вещь !a[$0]++в псевдокод, куда мы ее поместим? в случае если? а как насчет строки принять к сведению?

for every line:
  if !a[$0]++:   # check if have seen and take note at the same time?
    print line

проблема в том, что этот оператор ++делает две вещи. увеличить значение переменной, но вернуть исходное значение для дальнейшей обработки.

давайте попробуем разобрать ++. давай разбирать все подряд.

начнем с

result = !a[$0]++

убрать отрицатель

tmp = a[$0]++
result = !tmp

теперь разбираем ++на части. поскольку он выполняет две функции, мы делаем из него две строки.

tmp = a[$0]      # save the original value to use later
a[$0] = tmp + 1  # increment and store in variable
result = !tmp    # continue working with original value

теперь давайте попробуем вернуть это в псевдокод

for every line:
  tmp = a[$0]      # query if have seen line
  a[$0] = tmp + 1  # take note that has seen line
  if !tmp:         # decide whether to print line or not
    print line

вот и все. у нас есть цикл, если, печать, запрос и принять к сведению. только в другом порядке, чем предыдущий псевдокод.

сокращено до 8 символов

!a[$0]++

возможно из-за неявного цикла awks, неявного if, неявного print,и потому, что ++делает запрос и записывает одновременно.

Остается один вопрос. каково значение a[$0]для первой строки? или для любой линии, которая не была замечена ранее? ответ снова неявный.

любая переменная, которая используется впервые, неявно объявляется и инициализируется пустой строкой. поэтому значение a[$0]для первой строки является пустой строкой. то же самое для любых следующих строк, которые видны впервые.

++— числовой оператор. если задана строка, она преобразуется в число. пустая строка преобразуется в ноль. любая другая строка будет интерпретироваться как число некоторым алгоритмом наилучшего усилия.

!— логический оператор. если задано число или строка, оно преобразуется в логическое значение. число ноль ложно. пустая строка ложна. все остальное верно.

, так что это означает, что когда линия видна впервые, a[$0]не устанавливается. awk выполняет неявное действие и устанавливает пустую строку. пустая строка преобразуется в ноль из-за ++, а затем в false из-за !. результат !верен, поэтому строка печатается. значение в a[$0]теперь является числом 1 из ++.

если строка видна во второй раз, то a[$0]— это число 1, которое является истинным, а результат !— ложным, поэтому он не печатается. значение в a[$0]теперь равно 2.

любое дальнейшее столкновение с той же строкой увеличивает число. поскольку все числа, кроме нуля, верны, результат !всегда ложен, поэтому строка больше никогда не печатается.

именно так обнаруживаются дубликаты.

TL;DR:подсчитывает количество просмотров строки. если ноль, то напечатать. если любое другое число, то не печатать. он может быть коротким из-за множества имплицитов.


бонус :несколько вариантов одного вкладыша и очень краткое объяснение того, что он делает.

заменить$0(всю строку )на$2(второй столбец )удалит дубликаты, но только на основе второго столбца

$ cat input 
x y z
p q r
a y b

$ awk '!a[$2]++' input 
x y z
p q r

заменить!(отрицатель )на==1(равным единице )будут напечатаны только повторяющиеся строки

$ cat input 
a
b
c
b
c
c

$ awk 'a[$0]++==1' input 
b
c

заменить на>0(больше нуля )и добавить {print NR":"$0}будут напечатаны все повторяющиеся строки (, кроме исходной )с номером строки. NR— это специальная переменная awk, содержащая номер строки (, а точнее номер записи ).

$ awk 'a[$0]++>0 {print NR":"$0}' input 
4:b
5:c
6:c

для печати всех дубликатов, включая оригинал, потребуется гораздо больше изменений, потому что вам нужно отслеживать прошлые номера строк.

19
18.03.2021, 12:17

Теги

Похожие вопросы