How does awk '!a[$0]++' work?
This one-liner removes duplicate lines from text input without pre-sorting.
The original code I have found on the internets read:
This was even more perplexing to me as I took _ to have a special meaning in awk, like in Perl, but it turned out to be just a name of an array.
Now, I understand the logic behind the one-liner: each input line is used as a key in a hash array, thus, upon completion, the hash contains unique lines in the order of arrival.
What I would like to learn is how exactly this notation is interpreted by awk. E.g. what the bang sign ( ! ) means and the other elements of this code snippet.
How does it work?
4 Answers 4
Here is a "intuitive" answer, for a more in depth explanation of awk’s mechanism see either @Cuonglm’s
In this case, !a[$0]++ , the post-increment ++ can be set aside for a moment, it does not change the value of the expression. So, look at only !a[$0] . Here:
uses the current line $0 as key to the array a , taking the value stored there. If this particular key was never referenced before, a[$0] evaluates to the empty string.
The ! negates the value from before. If it was empty or zero (false), we now have a true result. If it was non-zero (true), we have a false result. If the whole expression evaluated to true, meaning that a[$0] was not set to begin with, the whole line is printed as the default action.
Also, regardless of the old value, the post-increment operator adds one to a[$0] , so the next time the same value in the array is accessed, it will be positive and the whole condition will fail.

Here is the processing:
a[$0] : look at the value of key $0 , in associative array a . If it does not exist, automatically create it with an empty string.
a[$0]++ : increment the value of a[$0] , return the old value as value of expression. The ++ operator returns a numeric value, so if a[$0] was empty to begin with, 0 is returned and a[$0] incremented to 1 .
!a[$0]++ : negate the value of expression. If a[$0]++ returned 0 (a false value), the whole expression evaluates to true, and makes awk perform the default action print $0 . Otherwise, if the whole expression evaluates to false, no further action is taken.
With gawk , we can use dgawk (or awk —debug with newer version) to debug a gawk script. First, create a gawk script, named test.awk :
In debugger console:
You can see, Op_postincrement was executed before Op_not .
You can also use si or stepi instead of s or step to see more clearly:
ah the ubiquitous but also ominous awk duplicate remover
this sweet baby is the love child of awk’s power and terseness. the pinacle of awk one liners. short but powerful and arcane all at once. removes duplicates while maintaining order. a feat unachieved by uniq or sort -u which removes only adjacent duplicates or has to break order to remove duplicates.
here is my attempt to explain how this awk one liner works. i took effort in explaining things so that someone who does not know any awk can still follow along. i hope i was able to do so.
first some pseudo code. what this one liner does is basically the following:
i hope you can see how this removes duplicates while maintaining order.
but how does a loop, an if, a print, and a mechanism for storing and retrieving strings fit in 8 characters of awk code? the answer is implicit.
the loop is implicit.
one of the core design philosophies of awk is that it does an implicit loop over every line of input. most code that you write in awk is inside this implicit loop.
the print is implicit.
is equivalent to this
$0 is the awk variable for the current line. print $0 means to print the current line.
the if is implict.
this thing !a[$0]++ < print $0 >is an awk "rule". a rule consists of a condition and a code block. this !a[$0]++ is the condition and this < print $0 >is the code block.
a typical awk program consists of one or more rules. for every input line awk tests the condition and if it is true it will execute the code block. if the condition is missing then it is implicit true. if the code block is missing then it is implicit < print $0 >.
so this thing !a[$0]++ is the conditional. based on which it is decided whether to print the line or not. and it somehow evaluates to true or false based on if the line is a duplicate or not? how does that work?
lets look at the pseudo code again
we understand the loop, the print, and the if. but how does it work so that it evaluates to false only at duplicate lines? and how does it take note of lines already seen?
let’s take apart this beast: !a[$0]++
if you know c or java you should already know some of the symbols. the semantics are identical or at least similar.
the exclamation mark ( ! ) is a negator. it evaluates the expression to a boolean and whatever the result it is negated. if the expression evaluates to true the end result is false and vice versa.
a[..] is an array. an associative array. other languages name it map or dictionary. in awk all arrays are associative arrays. the a has no special meaning. it is just a name for the array. it could just as well be x or eliminatetheduplicate .
$0 is the current line from the input. this is an awk specific variable.
the plus plus ( ++ ) is a post increment operator. this operator is a bit tricky because it does two things: the value in the variable is incremented. but it also "returns" a value for further processing. the value "returned" is the original, not incremented, value.
how do they work together?
roughly in this order:
- $0 is the current line
- a[$0] is the value in the array for the current line
- the post increment ( ++ ) gets the value from a[$0] ; increments and stores it back to a[$0] ; then "returns" the original value to the next operator in line: the negator.
- the negator ( ! ) gets a value from the ++ which was the original value from a[$0] ; it is evaluated to a boolean then negated then passed to the implicit if.
- the if then decides whether to print the line or not.
so that means whether the line gets printed or not, or in the context of this awk program: whether the line is a duplicate or not, is ultimately decided by the value in a[$0] .
by extension: the mechanism that is taking note whether this line has already been seen must then happen when ++ stores the incremented value back to a[$0] .
lets look at the pseudo code again
remember that this operator ++ does two things. increment the value in the variable and return the original value for further processing.
lets try to take the ++ apart. lets take the entire thing apart.
take the negator out
now we take the ++ apart. since it does two things we make two lines out of it.
now lets try to put that back in the pseudo code
so there we have it. we have the loop, the if, the print, the query and the note taking. just in a different order than the previous pseudo code.
condensed to 8 characters
possible because of awks implicit loop, implicit if, implicit print, and because the ++ does the query and note taking at the same time.
remains one question. what is the value of a[$0] for the first line? or for any line that has not been seen before? the answer is again implicit.
in awk any variable that is used for the first time is implicitly declared and initialized to an empty string. except arrays. arrays are declared and initialized to an empty array.
so when awk reaches this part: a[$0] it will first create an empty array. then it will initialize the item at $0 with an empty string.
so the value of a[$0] for the first line is the empty string. same for any following lines that are seen for the first time.
the ++ is a number operator. if given a string it is converted to a number. the empty string converts to zero. any other string would be interpreted as number by some best effort algorithm. zero if string is not a number.
the ! is a boolean operator. if given a number or a string it is converted to boolean. the number zero is false. the empty string is false. anything else is true.
so that means when a line is seen for the first time then a[$0] is not set. awk does the implicit and it is set to the empty string. the empty string is converted to zero because of ++ then to false because of ! . the result from ! is true so the line gets printed.
the value in a[$0] is now the number 1.
if a line is seen the second time then a[$0] is the number 1 which is true and the result from ! is false so it is not printed.
any further encounter of the same line increases the number. since all numbers except zero are true the result from ! will always be false so the line never gets printed again.
that is how the duplicates are detected.
TL;DR: it counts how often a line has been seen. if zero then print. if any other number then no print. it can be short because of many implicits.
bonus: some variants of the one liner and super short explanation of what it does.
replace $0 (entire line) with $2 (second column) will remove duplicates but only based on the second column
replace ! (negator) with ==1 (equal to one) and it will print lines that are duplicate (but only the first duplicate occurence)
replace with >0 (greater than zero) and add
Что означает 0 внутри команды awk
Awk — утилита и реализованный в ней сценарный язык для построчного разбора и редактирования текстового потока. Awk развила идеи таких утилит, как Sed и Grep, поэтому многое в ней похоже на них. Если эти утилиты вам знакомы, то освоение Awk для вас будет быстрым. По сравнению с Sed, язык Awk больше похож на Си и имеет множество его возможностей, такие как:
- объявление переменных и массивов с динамической типизацией;
- арифметические операции;
- ветвления и циклы;
- объявление функций и использование библиотеки встроенных функций.
Awk по сути является продолжателем Sed, привнося в процесс поточной обработки такие улучшения, как:
- автоматическая разбивка входящего фрагмента на поля, причем с возможностью смены разделителя на ходу;
- улучшенный процедурный язык программирования с переменными, циклами, функциями и условиями;
- множество встроенных переменных, облегчающих парсинг входящего фрагмента;
- возможность передавать переменные из вызова в программу.
Awk часто применяется для поиска в текстовых файлах характерных фрагментов; анализа текста и сбора статистики; написания простых утилит редактирования, когда использование компилируемого языка не рационально.
Данный учебник в популярной форме рассказывает об Awk и приемах работы в ней. Конечно данный учебник не может заменить официальную документацию, тем не менее, текст данной книги систематизирован и подает информацию от простого к сложному.
Awk разрабатывался много лет, поэтому на практике вы можете столкнуться с разными реализациями. Данное руководство больше опирается на реализацию GNU Awk, известную как GAWK. Расширения GAWK по тексту будут выделяться. Если по тексту мы говорим Awk, то описанное применимо для любой версии Awk, а не только для GAWK.
Содержание
Общие сведения [ править ]
Общий алгоритм работы [ править ]
Awk работает так:
- Исполняется цепочка всех блоков BEGIN , до начала обработки самого первого фрагмента.
- Весь текст разбивается на фрагменты по хранящемуся во встроенной переменной RS символу (обычно по символу переноса строки). Затем каждый фрагмент дополнительно разбивается на поля по символу FS (обычно это пробелы).
- К каждому фрагменту применяется Awk программа, состоящая из одного и более блоков.
- Обработка текста будет происходить, пока не кончатся все его фрагменты.
- Когда фрагменты данного текста закончились, исполняется цепочка блоков END .
- Если Awk было передано несколько файлов, то после завершения обработки одного файла начинается обработка следующего, и все предыдущие шаги повторяются.
Здесь и далее озвученные выше термины означают:
- Текст – множество символов, завершаемых символом конца потока EOF . Источником текста может служить простой файл или поток, передаваемый через специальный файл (например pipe-файл).
- Фрагмент – это часть текста или серия символов внутри текста, завершаемая одним или несколькими характерными, например символом переноса строки \n .
- Поле – часть фрагмента или серия символов внутри фрагмента, завершаемая одним или несколькими характерными, например пробельный символ.
Инструкции программы, собранные в блоки, в официальной документации Awk называют правилами (rules), но мы будем использовать такие термины, как программа Awk и блок на протяжении всего руководства, чтобы быть ближе к терминам, принятым в языке программирования Си.
Вы можете видеть, что алгоритм Awk местами очень похож на Sed. Но, в отличие от Sed, в Awk есть два программных блока, называемых BEGIN и END , которые выполняются один раз соответственно в начале и в конце обработки текста. Обычно в эти блоки закладываются общие для программы обработки моменты: например в BEGIN может быть заложена общая для программы обработки инициализация, а в блок END – подсчет статистик или информирование о том, что процедура завершена.
Также разбитие обрабатываемого фрагмента на поля отличает Awk от Sed и приносит дополнительные удобства, особенно когда структура входящего фрагмента известна. К разбитым полям можно обращаться по ссылкам вида $1 , $2 и так далее. Awk за один раз может разбить фрагмент только на 100 полей (если конкретная реализация не диктует иные ограничения). В отличие от большинства языков программирования, в Awk поля начинают отсчитываться с единицы, а не с нуля. В поле $0 записывается входящий фрагмент целиком. Число полей для текущего фрагмента сохраняется в переменной NF , при этом к последнему полю фрагмента всегда можно обратиться через ссылку $NF .
Переменная RS может быть проинициализирована пустой строкой. В этом случае фрагменты разделяются одним (или несколькими) пробелами. RS может изменяться по ходу программы: в этом случае изменение вступит в силу при обработке последующих фрагментов относительно текущего. Значение RS не обязательно должно быть единичным символом, это может быть регулярное выражение. Подстрока, которая получается по регулярному выражению, будет являться разделителем. Разделитель RS не будет являться ни концом текущей записи, ни началом следующей, т.е. RS как бы выпадает из текста.
Awk подсчитывает число записей, прочитанных им в текущем файле, в переменной FNR . Это значение сбрасывается для каждого последующего файла. Другая переменная NR хранит полное число прочитанных фрагментов за все время работы Awk, и оно никогда не сбрасывается.
Далее вы узнаете больше о языке программирования Awk и о встроенных полях.
Варианты вызова [ править ]
В простом случае вызов Awk выглядит так
В данном вызове Awk будет выполнять свою программу над каждым переданным файлом по порядку. Имя текущего файла сохраняется в переменной FILENAME .
Обрабатываемый текст может передаваться утилите и другими средствами командной оболочки:
Если Awk не получает в качестве ввода файл, то он читает просто STDIN , который обычно связывается с клавиатурой (интерактивный режим).
Писать программу в самом вызове имеет смысл, если она относительно небольшая. Для большой программы рекомендуется все же записывать ее в отдельный файл и вызывать через опцию -f . Рекомендуется файлам с программами для Awk добавлять расширение .awk .
Допустимо передавать несколько файлов через -f : в этом случае Awk сложит из них одну большую программу. Порядок при этом важен (подробнее об этом вы узнаете позже):
Awk может быть вызвана как исполняемая Awk-программа. Для этого нужно создать простой файл и в первой его строке указать башенг с awk-утилитой в качестве интерпретатора следующим образом
После этого нужно сделать файл исполняемым и запускать программу обычным методом.
Awk в нормальной ситуации возвращает 0, если программа не прервалась по оператору exit с иным кодом возврата. В случае ошибки возвращается 1. В случае фатальной ошибки возвращается 2.
Язык программирования Awk [ править ]
Комментарии [ править ]
В программах Awk допустимы однострочные комментарии, начинающиеся с символа решетки # . Комментарий начинается с этого символа и до конца строки.
Типы данных и переменные [ править ]
Как уже было сказано, синтаксис языка Awk был в основном заимствован у языка Си, но с динамической типизацией переменных, а также урезанным количеством встроенных типов данных.
Строго говоря, в Awk всего два типа данных: числовой и строковый. Чтобы определить переменную, достаточно инициализировать ее некоторым значением.
Первые реализации могли хранить строковые переменные только как восьмибитные ASCII-символы. На текущий момент проблем с кодировками нет в современных системах. В старых реализациях строковая переменная могла хранить не более 256 символов. В GAWK явных ограничений на длину нет.
Числовые значения могут быть записаны в экспоненциальной форме, при этом внутри используются вещественное представление удвоенной точности, например
Кроме строк и чисел также можно выделить константы регулярных выражений, которые должны быть записаны между двумя слэшами, например
Для обращения к переменной не нужны никакие дополнительные символы, достаточно просто обратиться к ней по имени. Имя переменной должно быть цепочкой символов из цифр, букв и символа нижнего подчеркивания, и не должно начинаться с цифры. Регистр в именах имеет значение, например var и VAR это разные переменные.
Чтобы Awk мог отличать имена переменных от литералов в выражениях, последние должны всегда закавычиваться.
В Awk имеется много встроенных переменных, которые имеют имена в верхнем регистре, например NF (хранит число полей текущего фрагмента), FS (хранит разделитель входящих полей). По этой причине рекомендуется объявлять переменные либо в нижнем регистре, либо в верблюжей нотации.
Кроме простых переменных, в Awk есть еще массивы, которые порождаются через их инициализацию:
Элементы массивов можно адресовать по строковым ключам (ассоциативные массивы), например
По умолчанию числовые переменные инициализируются нулем, а строки — пустой строкой. По этой причине не имеет смысла инициализировать каждую переменную явно, как это делается во многих языках программирования. Обращаться к еще неопределенным переменным и массивам тоже разрешено: в этом случае их определение будет побочным эффектом.
Массивы [ править ]
Массивы в Awk являются одномерными и динамическими. Правила для имен массивов аналогичны правилам для переменных. Единственное имя, которое не может использоваться ни для тех, ни для других это awk .
Массивы могут индексировать свои элементы по числовым индексам (простой массив) или по строковым ключам (ассоциативный массив). Хотя внешне кажется, что в Awk два типа массива, на самом деле внутренне простые массивы тоже ассоциативные.
В Awk единственный способ объявить массив это проинициализировать его первым значением:
Чтобы проверить, что в массиве существует элемент с определенным индексом, следует использовать оператор
Массивы обычно перебираются в цикле for (см. ниже)
Следует помнить, что внутренне реализация массивов строится на вычислениях хеш-сумм, поэтому порядок доступа к элементам из цикла зависит от того, как данные были помещены в массив, и не может быть никак изменен. Это может приводить к тому, что доступ к элементу, который был добавлен позже, будет происходить раньше и наоборот.
В больших программах иногда может понадобиться удаление элементов из массивов. Для этого используется специальный оператор
В Awk нет возможности создавать многомерные массивы, однако их можно эмулировать, например так
Правда для перебора таких массивов требуются вложенные циклы, через которые будут формироваться эти сложные ключи.
Преобразования данных [ править ]
Некоторые преобразования Awk умеет делать неявно:
- Числа всегда преобразуются в строки неявно, если они конкатенируются со строками. Чтобы принудить Awk к этому преобразованию явно, можно использовать прием конкатенации с пустой строкой «» .
- Строки в контексте арифметических операций всегда конвертируются неявно в числа.
- Строка интерпретируется всегда как число, если она состоит из цифр, точки, символа e / E и знаков + и — . Если строка не может быть конвертирована в число, то она превращается в 0.
Точные правила конвертации чисел определяются переменной CONVFMT , которая является по сути форматной строкой для системной функции sprintf . По умолчанию она инициализирована форматом %.6g , где формат g служит для сохранения значимых символов, а .6 означает сохранять 6 значимых знаков. Иногда приходится повышать точность через эту переменную, например в современных компьютерах двойная точность соответствует 16 или 17 десятичным цифрам.
До введения POSIX, awk предусматривало переменную OFMT , которая использовалась для печати чисел в операторе print и для преобразования в sprintf . Эта переменная также имеет значение по умолчанию %.6g . Такое поведение следует иметь в виду, если вы переносите старые Awk-программы на новую версию Awk.
Поля и работа с ними [ править ]
Как уже было сказано ранее, входящий фрагмент разбивается по разделителю FS (Input field separator) на поля. По умолчанию разделителем считается пробельный символ, под которым подразумеваются сразу три ASCII символа (один или несколько идущих подряд): пробел ( 0x20 ), горизонтальный табулятор ( 0x09 ) и символ новой строки ( 0x0A ). Во многих языках программирования под пробельным символом могут пониматься и другие управляющие последовательности, например 0xC (разрыв страницы) или 0xB (вертикальная табуляция), но не в Awk.
Для текущего фрагмента вы можете:
- Обратиться к любому полю через $<номер> , где <номер> – порядковый номер поля, начиная с 1. Соответственно в поле $0 хранится весь фрагмент.
- Узнать на сколько полей был разбит фрагмент через переменную NF (Number of fields).
- Обратиться к последнему полю через $NF . В данном случае здесь NF развернется в номер последнего поля автоматически.
- Вы можете вычислить номер поля, если используйте такой синтаксис $(<выражение>) , где результатом выражения должно являться число. Например $(NF-1) является обращением к предпоследнему полю, а $(1+2) – к третьему полю. Если поля с полученным номером не существует, то ссылка будет развернута в пустоту.
- После того как поля сформировались для текущего фрагмента, их можно редактировать, например $3 = $2-1 . Если вы редактируете одно из полей, входящий фрагмент будет вычислен заново так, чтобы полученное изменение применилось ко всему фрагменту. Это означает, что поле $0 в любом случае также изменится. Вы также можете изменить несуществующее поле: в этом случае оно будет добавлено ко входящему фрагменту. Любые добавления полей повлияют на счетчик NF , однако данный счетчик никогда не уменьшается, даже если вы затрете значение некоторого поля.
- Если вы изменяете поле вне контекста присваивания, то на входящий фрагмент это не будет влиять. Например $2-1 без присваивания не повлияет на поле $2 .
Разделителем FS может быть строка, состоящая из нескольких символов: в этом случае вся строка является разделителем. Переменная FS встроена в Awk и она не наследует каких-либо значений от командной оболочки неявно. Так, переменная POSIX IFS для разделения полей в Awk никак не используется.
Разделителем FS может быть регулярное выражение, когда разделитель динамический, либо его так проще объявить. Никаких особых правил записи такого разделителя в Awk нет: вы должны просто записать регулярное выражение в грамматике ERE, например
Разделителю можно присвоить пустую строку, тогда в этом случае большинство реализаций разобьют весь фрагмент по одному символу:
Но такое поведение было не всегда. В первых реализациях пустой разделитель означал, что разделителя нет, и фрагмент состоит из одного поля.
Разделитель полей может быть установлен из командной оболочки через опцию -F (не путать с -f , через который передается файл с программой Awk), например
Следует помнить о тонкостях разбивки на поля, если FS меняется по ходу программы:
- В POSIX предполагается, что фрагмент разбивается на поля сразу после чтения очередного фрагмента, однако на деле многие реализации в качестве оптимизации откладывают эту процедуру до первого обращения к полю. Этот момент следует выяснить сразу для конкретной реализации, чтобы не получить неожиданный результат в будущем. В GAWK фрагмент разбивается на поля в начале обработки без каких-либо оптимизаций.
- Предполагается, что Awk пользуется актуальным значением FS на момент разбивки. На практике это означает, что когда вы меняете FS во время обработки некоторого фрагмента, измененное значение FS будет использоваться уже на следующей итерации. Например сравните
У переменной FS есть парная ей OFS (Output field separator), которая позволяет транслировать входящий разделитель в другой для исходящей печати (когда это удобно) через оператор конкатенации , оператора print . На разбиение полей OFS никак не влияет.
Общая структура программы Awk [ править ]
Программа Awk строится из одного и более блоков, границы которых обозначают фигурные скобки. Общая структура программы в более-менее полном виде выглядит так
Любая Awk программа строится по меньшей мере из одного основного блока, в который помещаются инструкции, применяемые к каждому фрагменту, если блок не имеет шаблона (см. ниже).
Помимо основного блока, есть еще два необязательных: BEGIN и END . Блок BEGIN исполняется один раз в начале обработки каждого текста. Часто туда закладывается начальная инициализация, которая актуальна на время обработки всего текста. Например, если бы задачей Awk было разбиение полей фрагмента по колонкам, то в блок BEGIN логично поместить процедуру вывода шапки для этих колонок.
Второй блок END хранит инструкции, которые выполняются один раз после обработки всех фрагментов текста. Туда можно закладывать подсчеты всевозможных статистик, очистку окружения от мусора, отправку сообщений по почте о завершении процесса обработки текста и прочее.
Ранние реализации поддерживали по одному блоку BEGIN и END для одной программы Awk. Версии, выпущенные после 1987, не ограничивают в числе блоков. Кроме того, их можно перемешивать как угодно в исходном коде, причем исполнятся блоки BEGIN и END будут в том порядке, в котором они обнаруживаются Awk. Очень часто BEGIN и END блоки присоединяются к основной программе снаружи, чтобы не дублировать код (так называемые, библиотеки Awk). В этом случае следует закладывать структуру так, чтобы порядок не влиял на основной алгоритм.
Если программа имеет всего один блок BEGIN , то программа завершается сразу после его исполнения, независимо от числа переданных фрагментов. Ранние реализации Awk в этой ситуации не игнорировали фрагменты, а просто, видя что нет основной программы, их по порядку пропускали, при этом все внутренние переменные актуализировались. С целью оптимизации, последние версии Awk полностью игнорируют ввод в такой ситуации.
Если в программе есть один блок BEGIN и один END , но нет основного блока, то Awk всегда пройдется по всем фрагментам. Это нужно потому, что потенциально в блоке END может быть заложен анализ переменных по типу NR (полное число прочитанных фрагментов).
В общем случае основной блок может быть пустым, но блоки BEGIN и END пустыми быть не могут – поведение для пустых BEGIN и END неопределенное в общем случае.
Существует несколько тонкостей, о которых следует помнить:
- В блоке BEGIN нет смысла обращаться к полям фрагмента, так как он исполняется до начала чтения самого первого фрагмента. Любые обращения к полям или переменным, завязанным на них, будут возвращать пустоту или ноль.
- В блоке END по логике также нет смысла обращаться к полям и переменным, завязанным на них, потому что к этому моменту последний фрагмент уже обработан. В старых реализациях обращения к полям и переменным, завязанных на них, могут возвращать пустоту или ноль в блоке END , однако, например, в GAWK обращение к $0 вернет последний фрагмент, а NF – число полей в нем. Так как эта ситуация неоднозначна, следует избегать обращений к полям в блоке END .
- По причине первых двух пунктов, вызов print без аргументов может приводить в разных реализациях либо к печати фрагмента ( print $0 ), либо пустой строки. Вызывать print без аргументов в BEGIN и END является порочной практикой, поэтому в этих блоках настоятельно рекомендуется вызывать только print «» для печати пустой строки.
Ниже вы узнаете о пользовательских функциях. Они могут объявляться в любом месте программы, в том числе между блоками.
Основные блоки программы [ править ]
Программа Awk строится из одного и более обычных блоков. На самом деле блочное устройство Awk было заимствовано у Sed, разве что фигурные скобки в Awk нужно ставить всегда. Общий синтаксис блока выглядит так
Программа Awk пытается применить шаблон блока к фрагменту или проверить условие, и если шаблон совпадает или условие ИСТИНА, то исполняются команды этого блока. Если блок не имеет явного шаблона или условия, то он будет применяться ко всем фрагментам. Обычные блоки можно мешать с блоками BEGIN и END и, если пожелаете, записывать в одну строку. Между блоками никакие разделители не используются, так как фигурные скобки не создают неоднозначности.
Обычный блок может быть пустым, хотя это и лишено смысла. Также блок можно опустить, оставив только шаблон или условие. В этом случае подразумевается блок с командой print $0 , т.е.
Обычные блоки применяются к фрагменту в том порядке, в котором они записаны в программе. Это можно показать на следующем примере
В этом примере у нас три блока. Первый и третий блок не имеют шаблона, а второй имеет шаблон, который проверяет состоит ли входящий фрагмент только из цифровых знаков. На вход этой программе мы передаем три символа: букву a , цифру 2 и букву b . Вы можете видеть, что для букв применились только 1 и 3 блок, а для цифры все три блока.
Две инструкции, записанные в одну строку, внутри любого блока должны отделяться друг от друга точкой с запятой, причем для последней инструкции точка с запятой не обязательно ставить.
Каждый обычный блок создает свою изолированную область видимости для переменных, другими словами, переменная, объявленная в некотором блоке, видна только в его пределах. Единственный блок, чьи переменные видят все остальные, это блок BEGIN . Давайте рассмотрим такой пример
В этом примере два обычных блока имеют по своему экземпляру переменной с именем var . В этом примере мы из простых блоков обратились к глобальным переменным global_b и global_1 , объявленным в блоке BEGIN . Обратите внимание, что изменение глобальной переменной в некотором блоке ( global_1 ) приведет к ее изменению в оставшихся блоках, которые как то к ней обращаются.
Блоки можно вкладывать. Вложенные блоки главным образом используются для объединения инструкций, относящихся к конструкциям проверки условий if..else или циклам. В отличие от языка Си, вложенные блоки не создают еще одну область видимости. Например, сравните
Диапазоны в блоках [ править ]
Как и в Sed к блоку вместо простого шаблона можно применить диапазон. Напомним, что диапазон позволяет описать не одну строку, а сразу несколько идущих подряд. Общий синтаксис в этом случае такой
Общий принцип работы по диапазону такой. Awk пытается найти фрагмент, соответствующий шаблону, обозначающему начало диапазона. Если такой фрагмент был найден, блок диапазона как бы становится активным и будет выполняться до тех пор, пока он не упрется во фрагмент, соответствующий шаблону конца диапазона. Отметим, что текст может закончиться раньше, чем появится фрагмент, соответствующий шаблону конца диапазона, тогда блок диапазона будет выполняться для всего подряд после начального фрагмента диапазона.
Рассмотрим такой пример
В Awk нет варианта объявления диапазона через порядковые номера фрагментов, как это можно делать в Sed. Вероятно так сделано из-за другого подхода к чтению входящего потока, либо потому, что все потребности можно покрыть регулярными выражениями.
Операции [ править ]
- Общие
- Сложение +
- Унарный плюс +
- Конкатенация строк («строка» «строка»)
- Вычитание —
- Унарное отрицание —
- Умножение *
- Возведение в степень ^ или ** (GAWK) (по POSIX только ^ )
- Деление /
- Остаток от деления %
- Префиксный/постфиксный инкремент ++
- Префиксный/постфиксный декремент —
- Группировка для изменения приоритетов операторов ()
- Простое присваивание =
- Присваивание с операцией: += -= *= /= %= ^= **= (GAWK)
- Больше > и больше либо равно >=
- Меньше < и меньше либо равно <=
- Равно ==
- Не равно !=
- Логическое ИЛИ ||
- Логическое И &&
- Логическое НЕ !
- next
Оператор принуждает Awk прекратить обработку текущего фрагмента и перейти к следующему. Это означает, что все команды, следующие за опреатором, будут пропущены для текущего фрагмента. По POSIX использование этого оператора в блоках BEGIN и END приводит к неопределенному поведению. Многие реализации запрещают использование этого оператора в пользовательских функциях. В GAWK такой проблемы нет. Если вызов оператора приводит к концу ввода, то происходит исполнение блока END . - nextfile
Если Awk передается на обработку несколько файлов, то данный оператор принуждает прекратить обработку текущего и перейти к следующему. Если вызов оператора приводит к концу ввода, то происходит исполнение блока END . Этот оператор является расширением GAWK и скорее всего будет отсутствовать в других реализациях. Обратите внимание, что до GAWK 3.0 этот оператор писался в два слова ( next file ), а после он стал писаться в одно слово. Такой вызов еще поддерживается, однако GAWK будет выводить предупреждение, и возможно в будущем такой вызов будет вообще запрещен. - exit
Оператор требует немедленно прекратить выполнение и выйти из программы, при этом будет исполнен блок END , если он есть. Если он вызывается в блоке BEGIN , то весь последующий код будет пропущен, однако, если есть блок END , то он выполнится. Вызов в блоке END требует немедленное прекращение программы. У данного оператора есть аргумент в виде возвращаемого кода возврата, в противном случае будет возвращаться нулевой код. Ненулевой код иногда используется, чтобы пропускать блок END , где он может быть проверен.
Левой частью присваивания может быть переменная или поле. В Awk допускается присваивание по цепочке, например
В этом примере по цепочке справа налево будет присвоено значение 5 всем трем переменным. Присваивание допустимо в любой части r-value выражения, например так
Однако такие записи впоследствии очень сложно читать, поэтому такой прием не нужно брать за правило или использовать в больших программах.
В Awk нет специальных булевых типов и здесь все заимствуется у Си. Так, условно ИСТИНОЙ считается ненулевое значение числа и не пустая строка, иначе значение считается ЛОЖЬЮ.
Ниже для справки приведены приоритеты выполнения операторов в выражениях, от наивысшего к наименьшему:
- () , $<поле>
- ++ , —
- ^ , ( ** )
- унарный + , унарный — , логическое НЕ
- * , / , %
- + , —
Условные выражения [ править ]
В Awk нет специального условного типа данных, т.е. условные операторы работают с имеющимися типами данных напрямую. ИСТИНОЙ считается любое ненулевое число и любая не пустая строка, соответственно все остальное это ЛОЖЬ.
Логические выражения, построенные на логических операторах, возвращают 0, если результатом является ЛОЖЬ, и 1 — если ИСТИНА. Обычно логические выражения используются в конструкциях if..else , циклах и в условиях перед обычными блоками.
Ниже представлены примеры некоторых логических выражений:
Регулярные выражения [ править ]
Регулярные выражения позволяют описать в общем множество искомых подстрок для проверки их соответствия (или несоответствия) в условных выражениях. Регулярные выражения в Awk записываются в виде константных строк между двумя слешами ( /regexp/ ). В такой форме они обычно используются в условных выражениях, когда не требуется их передавать через переменные.
Переменные являются вторым способом передачи регулярных выражений. В этом случае они могут быть записаны как обычные строки и называются динамическими.
Awk использует диалект регулярных выражений, похожий на ERE. Однако в GAWK этот диалект немного расширен за счет классов литералов, очень похожих на те что есть в диалекте Perl.
Ниже перечислены все метасимволы традиционных регулярных выражений
Следующие метасимволы являются расширением GAWK.
- Пустое регулярное выражение // соответствует пустой строке, в которой начало есть конец.
- Константные регулярные выражения следует использовать, когда используются операторы
Управление печатью Awk [ править ]
Для печати в Awk предусмотрено два оператора: print и printf . Оператор print является упрощенной версией printf и производит печать в стандартном формате. Для печати с помощью print вам нужно указать только что печатать. В простом случае его вызов выглядит так
где элементами могут быть строковые литералы, переменные, поля, в общем все, что может быть сконвертировано в строку. В общем случае print не требует аргументов: тогда он просто напечатает поле $0 .
Ниже приведены примеры вызова оператора print .
Очень частой ошибкой является ситуация, когда при печати литерала, пользователь забывает двойные кавычки и не может понять, почему ничего не выводится, так как Awk интерпретирует строки как переменные.
Мы уже упомянули о разделителе OFS , который вставляется между элементами печати. Во время вывода используется еще один разделитель ORS (Output record separator), который вставляется в конец всего, что напечаталось в одном вызове print . По умолчанию там хранится символ новой строки, именно поэтому каждая печать начинается с новой строки.
Хотя в print не указывается форматная строка, все же форматом можно управлять через встроенную переменную OFMT . Она используется во время конвертации числа в строковую переменную и по умолчанию имеет значение %.6g . Используя правила объявления формата для стандартной функции библиотеки Си sprintf , вы можете менять точность выводимых вещественных чисел, например
Обратите внимание, что в предыдущем примере мы складываем входящее поле с нулем, чтобы принудить Awk сконвертировать входящую строку в число, а затем снова в строку во время печати. Без этого к сожалению ничего не получится.
Форматированная печать с помощью printf [ править ]
В сложных ситуациях может понадобится форматированный вывод, в котором все аспекты контролируются так называемой форматной строкой. Для тех, кто когда-либо писал программы на Си и использовал стандартную библиотеку ввода/вывода, правила написания форматной строки будут знакомы.
Общий вызов printf выглядит так
Форматная строка объясняет printf структуру выводимой строки с помощью специальных символов, называемых форматами, которые впоследствии заменяются на передаваемые элементы. Каждому формату сопоставляется ровно один элемент слева направо со второго аргумента.
Любой формат начинается с символа % , после которого минимально идет буква, называющая тип данных для данного формата. Все форматы в неизменном виде перекочевали из стандартной библиотеки Си, но в урезанном виде, так как в Awk нет такого многообразия типов данных. Ниже перечислены возможные форматы:
- %c (char) – единичный символ.
- %s (string) – используется для строк.
- %d или %i (decimal) – знаковое десятичное целое число.
- %u (unsigned decimal) – беззнаковое целое десятичное число. Беззнаковое означает, что в двоичном представлении числа старший бит не тратится на хранение знака, благодаря чему удваивается диапазон представления положительных чисел.
- %o (octal) – число в восьмеричной системе счисления. Для этого формата, кроме конвертации, будет подписываться ведущий ноль.
- %x или %X (hexdecimal) – число в щестнадцатеричной системе счисления, причем для %X символы A-F выводятся в верхнем регистре. Для этого формата, кроме конвертации, будет подписываться префикс 0x .
- %e или %E (exponential) – вещественное число будет представлено в экспоненциальной форме, причем для %E символ экспоненты будет записан в верхнем регистре.
- %f или %g или %G (float) – число с плавающей запятой. Формат %g позволяет Awk выбрать для числа наиболее компактную запись между %f или %e . Вариант %G соответственно делает выбор между %f и %E .
К формату может применяться один или несколько модификаторов, которые записываются перед буквой. Разные форматы поддерживают разные модификаторы.
- Модификатор + . Используется в числовых форматах. Требует всегда печатать знак + для положительного числа.
- Модификатор — . Используется в строковых форматах, чтобы выравнивать текст в выводимом поле по левому краю. По умолчанию текст выводится по правому краю.
- Пробел (например % d ). Используется в числовых форматах, чтобы выделить знакоместо под знак числа. Если число положительное или равняется нулю, то вместо знака будет вставлен пробел, а если отрицательное – знак минус. Используется, когда требуется тонкое выравнивание в колонке.
- Ведущий ноль (например %0d ). Используется в числовых форматах, чтобы заполнить выводимое поле ведущими нулями.
- Ширина поля (например %8d ). На самом деле каждый конкретный формат выводится в невидимом поле, размер которого по умолчанию подстраивается под его содержимое. Если вам нужно чтобы формат тратил фиксированное число знакомест, используйте ширину формата. Отметим, что ширина формата задает минимальный размер поля, т.е. если содержимое не будет влезать, то в любом случае будут задействованы дополнительные знакоместа.
- Точность (например %.6f ). Используется для форматов вещественных и целых чисел. Для форматов %e , %E и %f точность означает сколько десятичных разрядов нужно выводить после десятичной точки. Для форматов %g и %G точность означает максимальное число значащих цифр. Для форматов целых чисел точность означает минимальное количество печатаемых символов. Точность может использоваться для строкового формата: в этом случае она работает как жесткий ограничитель на количество выводимых символов, задавая максимальный размер поля.
Ниже приведены несколько поучительных примеров использования форматированного вывода.
Обратите внимание, что в заголовке мы выделили 3 колонки, где имя колонки выравнено по правому краю, средняя – по центу и правая – по левому краю. Среднюю колонку мы выравнивали за счет двух форматов, в первый из которых помещается заголовок, а во второй помещается пробел. Далее мы выводим символы подчеркивания без явного форматирования. Далее мы 3 раза выводим одни и те же данные, где поле первой колонки выводится как беззнаковое целое на 4 знакоместа максимум, второе поле на 16 знакомест и третье поле на 8 знакомест. Обратите внимание, что телефон на самом деле имеет больше 8 символов, однако поле жестко ограничено по знакоместам, поэтому три последних символа обрезаются. Также обратите внимание, что мы специально указываем минимальное число знакомест (число до точки), чтобы поле заполнилось пробелами или, в случае беззнакового целого, нулями.
На практике форматная строка обычно передается через переменную, а не вводится каждый раз.
Динамическая ширина поля [ править ]
Модификатор ширины поля может быть вынесен за форматную строку, и в таком случае ширину поля можно определять через числовую переменную. Этот прием может использоваться со всеми типами данных, но чаще всего его используют со строками.
Чтобы вынести ширину поля за форматную строку, нужно в позиции модификатора просто поставить символ звездочки * , а ширину поля передавать дополнительным аргументом (перед данными). Все остальное остается неизменным. Ниже приведено несколько примеров.
Перенаправление печати [ править ]
По умолчанию print и printf выводят данные в STDOUT . Тем не менее, вы можете перенаправлять их вывод в отдельные файлы, используя синтаксис близкий к перенаправлению потоков в командной оболочке:
- > «<путь-к-файлу>»
Перенаправляет поток вывода в файл, при этом если файл не пустой, то он будет перезаписан. - >> «<путь-к-файлу>»
Перенаправляет поток вывода в файл с режимом на дозапись, если файл не пустой.
Обратите внимание, что имена файлов всегда нужно закавычивать, если вы передаете путь литералом.
Кроме простого перенаправления, данные можно посылать другой команде через конвейер прямо из Awk. При этом конвейерный pipe-файл будет открыт из Awk.
Следующий пример демонстрирует использование конвейера
Мы передаем Awk латинский алфавит, записанный в обратном порядке. Далее мы инициализируем переменную cmd командой rev , которая отзеркаливает переданную ей строку, в результате чего мы получаем алфавит, записанный в прямом порядке. Обратите внимание, что мы вынуждены передавать команду через переменную, так как внутренне Awk открывает дескриптор на pipe-файл, используя команду как идентификатор. Этот pipe остается открытым на протяжении всей работы Awk, поэтому хорошим тонном является его закрытие сразу после его использования. Обратите внимание, что команда на правом конце конвейера будет работать до закрытия pipe-файла (т.е. до закрытия Awk или до явного закрытия pipe-файла). Также следует помнить, что если бы у нас было несколько строк, то pipe-файл открывался бы каждый раз заново для каждой новой строки из-за close() . Если мы опустим close() , то каждая поступившая строка будет передана rev через один и тот же pipe-файл. Многое может зависеть от программы на правом конце конвейера, поэтому используя эту технику, вы должны представлять, что вы делаете.
Перенаправления тоже открывают дескрипторы на файлы и их необходимо освобождать, если Awk программа исполняется сравнительно долго. Для закрытия дескрипторов необходимо использовать опять же close() .
Многие реализации Awk разрешают открыть за один раз только один конвейер и какое-то число дескрипторов для перенаправления (обычно ограничение на открытые дескрипторы накладывается через пользователя, запустившего программу). В реализации GAWK ограничений на конвейеры нет, однако в любом случае старайтесь экономить ресурсы системы. Также в GAWK может включаться внутренний механизм мультиплексирования, если вы достигли предела по открытым дескрипторам. Эта техника потенциально непереносима между системами, поэтому просто не забывайте вовремя закрывать дескрипторы.
Функция close() возвращает 0, если дескриптор успешно закрыт, иначе она возвращает не ноль и записывает в переменную ERRNO строку с описанием ошибки.
Функция sprintf() [ править ]
Операторы print и printf могут выводить строки только в файл и не могут являться частью r-value выражения. Было бы здорово, если бы формируемые строки с помощью формата можно было бы присваивать переменным. Именно для этого используется функция sprintf() , синтаксис которой имеет вид
Вызов этой функции возвращает строку в соответствии с форматом, при этом абсолютно все правила, описанные нами ранее для printf , справедливы и для этой функции. Ниже приведено несколько примеров.
Ветвления [ править ]
Если условное выражение ИСТИНА, то исполняются инструкция в ветви 1, иначе исполняется инструкция ветви 2. В общем случае ветвь else не обязательна. Чтобы поместить в какую-либо ветвь больше одной инструкции, их следует поместить в фигурные скобки (т.е. в блок).
Ветвей может быть больше двух, если ветвления вкладывать по аналогии с языком Си
Если else стоит с инструкцией предыдущей ветви в одной строке и она не выделена фигурными скобками, то чтобы отделить else , нужно использовать точку с запятой:
Упомянем также наличие тернарной конструкции
Тернарная конструкция обычно используется для компактного присваивания значения переменной, когда это значение зависит от условия
Циклы [ править ]
В Awk есть три типа циклов:
- for
- while
- do-while
а также два управляющих для циклов слова:
- break
- continue
Цикл for [ править ]
Цикл for строится так
Цикл for повторяет инструкции тела до тех пор, пока условие блока повторения остается истинным. В блоке инициализации обычно определяются счетчики цикла, которые участвуют в условии. Блок инициализации выполняется один раз перед входом в цикл.
В блоке инкремента обычно наращиваются счетчики, чтобы в конечном итоге сделать условие в блоке повторения ложным рано или поздно. Блок инкремента выполняется каждый раз в конце очередного повторения.
Вообще в этом цикле все блоки являются необязательными: в этом случае цикл становится бесконечным.
Вы также можете указать только условие
однако такие циклы лучше записывать через while .
Ниже приведен пример вывода таблицы умножения, где используется два цикла for
Вывод будет следующий
Существует форма цикла for , удобная для перебора массивов
Такие циклы сами контролируют выход индекса за пределы массива.
Цикл while [ править ]
Цикл while строится так
Цикл while исполняется до тех пор, пока условие ИСТИННО. Условие проверяется на входе каждой новой итерации.
Цикл do-while работает похоже, но условие проверяется не в начале входа в цикл, а в конце. Таким образом, цикл всегда выполняется минимум один раз.
Управляющие операторы циклов [ править ]
Управляющий оператор break позволяет прервать цикл на текущем шаге итерации и переместить точку следования на следующую команду за циклом. Оператор continue прерывает исполнение итерации цикла в текущей точке и перемещает точку следования в начало новой итерации, тем самым пропуская оставшиеся команды.
Хотя использование этих операторов вне циклов не имеет смысла, исторически такой вызов трактовался как оператор next . Стандарт POSIX требует, чтобы эти операторы использовались только в циклах, поэтому последние версии Awk будут генерировать синтаксическую ошибку. В GAWK можно включить историческое поведение, если вызывать команду с опцией —traditional .
Пользовательские функции [ править ]
В Awk можно объявлять собственные функции, чтобы группировать повторяющийся однотипный код. Определения функций могут располагаться где угодно между блоками, причем функция может быть объявлена после ее первого вызова, так как Awk читает программу целиком прежде чем ее исполнить.
Синтаксис определения выглядит так
Правила объявления имен функций аналогичны правилам именования переменных. Список аргументов функции представляет собой передаваемые локальные для функции переменные, перечисленные через запятую. Функция не может иметь двух аргументов с одинаковым именем.
Во время исполнения функции ее локальные переменные, одноименные с теми, что объявлены вне функции, затеняют их. Ко всем не затененным переменным можно обращаться из функции без каких либо ограничений.
Вообще переменные в списке являются необязательными, и если они явно не передаются, то они будут проинициализированы значениями по умолчанию.
Внутри функции можно вызывать другие функции. Также можно вызывать текущую функцию рекурсивно. Во многих реализациях Awk (в том числе и в GAWK) ключевое слово function может быть сокращено до func . Однако в POSIX определена только function . Использование func в POSIX-совместимом режиме может приводить к неожиданным ошибкам, поэтому использование func в целом не рекомендуется.
Некоторые реализации Awk разрешают вызов неопределенной функции, если фактического вызова не происходит. В GAWK можно разрешить вывод предупреждений о вызовах неопределенных функций, если вызывать команду с опцией —lint .
В некоторых реализациях Awk запрещено использовать оператор next внутри функций. В GAWK такой проблемы нет.
Тело функции может возвращать код возврата через оператор return , либо прервать исполнение функции в некоторой ее точке. Оператор return имеет необязательный аргумент в виде целого выражения. Если функция не возвращает ничего, то такую функцию называют процедурой, а попытка перехвата значения может приводить к непредсказуемому поведению.
Встроенные функции [ править ]
В Awk встроено несколько функций, облегчающих программирование. Далее приводится краткое описание всех базовых встроенных функций. Необязательные аргументы отмечены квадратными скобками. Пропуск обязательных аргументов делает вызов функции некорректным и всегда приводит к ошибкам.
Отметим, что некоторая реализация может добавлять свои функции, поэтому обязательно прочитайте документацию к реализации Awk, которую вы используете.
Функции для работы с числами [ править ]
Функции для работы со строками [ править ]
Функции для работы с вводом/выводом [ править ]
Функции для работы с датой и временем [ править ]
Команда getline [ править ]
Рядовой пользователь обычно не вмешивается в процедуру чтения фрагментов. Тем не менее, в Awk заложена встроенная команда getline , позволяющая явно управлять чтением входящего текста. Эта команда не должна использоваться новичками, плохо понимающими процедуру чтения текста, а также вы всегда должны представлять, что вы делаете.
Команда getline обычно используется, когда текст имеет логическую структуру и простого разбиения его на фрагменты недостаточно.
Команде нужно передать одну или несколько переменных, в которые она попытается записать следующий по отношению к текущему фрагмент. Если ей это удается, то она возвращает не ноль; если ничего не прочитано — 0; и возвращает -1 в случае ошибки и инициализирует ERRNO сообщением об ошибке. Команда может вызываться и без аргументов, тогда новый фрагмент следует брать из поля $0 .
Понять, когда getline может использоваться, легче всего на конкретных примерах.
Следующая Awk программа позволяет удалять многострочные Си комментарии из текста.
Пусть у нас есть такой файл с исходным кодом на языке Си:
Прогоним его через программу Awk, чтобы увидеть как комментарии удаляться.
Обратите внимание, что вызов getline неявно обновляет $0 , NF , NR , FNR и RT , т.е. в этом блоке оригинальное значение $0 будет потеряно.
Следующая программа не имеет конкретной цели, а просто демонстрирует что будет, если передать getline аргумент.
Еще раз обратим внимание, что при передаче getline аргумента, неявно обновятся только NR , FNR и RT , но не $0 .
Команде getline можно передавать файл через поток ввода. Это позволяет обойти главный поток ввода Awk, чтобы, например, читать два источника за один раз.
При таком чтении переменные NR и FNR не обновляются, так как они привязаны к главному потоку ввода Awk, но $0 , NF и RT будут обновляться в обычной манере.
Путь к файлу можно формировать из переменных. В этом случае, чтобы программа был портируема между реализациями Awk, следует делать конкатенацию фрагментов через круглые скобки:
Соответственно комбинировать вариант перенаправления с передачей переменной также можно
Команда getline можно принимать данные также по конвейеру, например
Поведение в таком случае аналогично, как и в случае перенаправления файла через поток ввода. Команда может формироваться из литералов. В этом случае вы должны всегда конкатенировать их через круглые скобки, например
Используя предыдущий подход, можно запрашивать из Awk-программы данные у различных системных утилит и записывать результаты в переменные, например
Документация по реализации getline в GAWK рекомендует помнить о следующих моментах, связанных с этой командой:
- Когда getline правит $0 и все, что с ней связано, не происходит автоматического перехода в начало Awk-программы с чтением нового фрагмента. Вы это должны контролировать самостоятельно с помощью оператора next .
- Старые реализации Awk не разрешают за один раз использовать больше одного pipe-файла. В GAWK такой проблемы нет, и все зависит от ограничений самой системы.
- Вызов getline без перенаправления не рекомендуется использовать в блоках BEGIN , так как в этом случае входящий поток формально еще не готов для чтения.
- Не следует вызывать getline как getline < FILENAME , так как в этом случае возникает гонка за один и тот же файл из разных потоков.
- Следует осторожно использовать getline , если ей передается аргумент выражением, в котором есть побочные эффекты. Положим такую ситуацию
Расширения GAWK [ править ]
Реализация GAWK очень продвинута по сравнению с первыми реализациями Awk. В этой главе мы попытаемся раскрыть некоторые крайне полезные расширения реализации GAWK.
Включение других файлов в программу [ править ]
Эта особенность используется, чтобы выносить повторяющиеся и редко изменяющиеся типовые части программы в отдельные файлы. Такие файлы можно собирать в библиотеки и повторно использовать в новых Awk программах. На практике обычно имеет смысл выносить только блоки BEGIN и END , однако это только рекомендация.
Чтобы включить один файл Awk в другой файл нужно использовать встроенную директиву
Директива похожа на макрос
в языке Си и работает похожим образом, т.е. подставляет содержимое указанного файла в текущий код.
Кроме этой директивы, можно воспользоваться опцией -i в самом вызове GAWK. Имя файла всегда должно быть литералом, т.е. переменные с директивой @include использовать нельзя.
Если путь к файлу не является абсолютным, то GAWK использует переменную окружения AWKPATH для поиска файлов, которая по умолчанию пустая. В простом случае Awk просматривает текущую рабочую директорию.
Пусть у нас есть два файла
Во второй файл мы включим библиотечный
Теперь вызовем основную программу
Раз мы заговорили о включениях, то есть еще один метод, основанный на множественном использовании опции -f в вызове GAWK. Этот метод нельзя использовать, если в коде есть директивы @include , потому что в этом случае потенциально может произойти петля включения. Обычно этот метод используется, когда программа состоит из небольшого числа файлов и, вероятно, организована через пространства имен (о которых мы поговорим ниже).
Пусть у нас есть такие файлы:
Тогда мы могли бы вызвать их комбинацию так
Обратите внимание, что в обоих методах четко прослеживается принцип порядка включения. Если при использовании @include порядок определяется статически, то с опцией -f порядок может зависеть от вызова.
Пространства имен [ править ]
По умолчанию все объекты Awk-программы сосуществуют в одном единственном пространстве имен, или другими словами, имена этих объектов должны быть уникальными для их совместного сосуществования. Хотя такое встречается редко в Awk, на практике вам может понадобиться использовать разные реализации функции с одним и тем же именем, либо отделить одноименные глобальные переменные, приходящие из разных библиотечных файлов. Вы можете переименовать совпадающие по именам объекты, либо воспользоваться пространствами имен, чем-то похожими на пространства имен C++. Пространства имен появились в GAWK, начиная с 5-й версии.
Для объявления пространства имен служит директива
Чтобы обратиться к объекту некоторого пространства имен, используется С++-подобный синтаксис:
В данном случае объектами могут быть глобальные переменные и функции. В GAWK по умолчанию уже создано одно пространство имен с именем awk , которое всегда подразумевается, если в вызове пространство имен не указано.
Пространства имен в GAWK плоские, т.е. их нельзя вкладывать, как это можно делать в C++, да и это в GAWK по большому счету и не нужно.
Пространство имен действует от директивы и до следующей директивы, либо до конца файла. В одном файле пространство имен можно менять сколько угодно раз, однако такой подход может осложнить чтение исходного кода, поэтому старайтесь следовать принципу один исходный файл – одно пространство.
Пространства имен не влияют на порядок выполнения блоков.
Давайте рассмотрим принцип размещения объектов в пространствах имен на следующем примере.
Теперь запишем такой код в основной программе
Конструкция switch..case [ править ]
Конструкция языка Си switch..case не поддерживается в оригинальном Awk, однако, вероятно для полноты, ее поддержку включили в GAWK. Напомним, что данная конструкция позволяет облегчить написание цепочки проверки условий, если используется условие типа проверка по образцу. Синтаксис в GAWK абсолютно идентичен синтаксису в Си:
Напомним как это работает. Входящее значение по цепочке сравнивается с образцами после ключевого слова case . Если значение совпадает с образцом, то выполняются инструкции. Обычно инструкции завершает оператор break , чтобы исполнение не перенеслось на нижестоящий case . Иногда переход на нижестоящий case желателен, например когда часть команд для этих блоков одинаковая. Если в конструкции есть необязательный блок default , всегда стоящий последним, то исполняется он, когда не найдено ни одного подходящего образца.
В отличие от Си, образцами в GAWK могут выступать как простые литералы и числа, так и регулярные выражения.
Блоки BEGINFILE и ENDFILE [ править ]
В GAWK помимо прочих блоков, есть еще два специальных: BEGINFILE и ENDFILE .
Блок BEGINFILE исполняется один раз прямо перед обработкой самого первого фрагмента текста. Соответственно ENDFILE вызывается после обработки самого последнего фрагмента файла.
Следующая программа демонстрирует порядок вызова специальных блоков.
Эти блоки были придуманы для сугубо технологических целей. Например в BEGINFILE может быть заложена проверка возможности продолжать обработку, так как файл вообще говоря может хранить бинарные данные. Вы можете заложить оператор nextfile , если это бинарный файл, чтобы не прерывать исполнение по фатальной ошибке. В ENDFILE можно закладывать процедуры освобождения ресурсов системы.
Сопроцессы [ править ]
Еще одна интересная техника, позволяющая делать из GAWK простейший клиент. Для этого GAWK может открыть двусторонний канал между собой и другим процессом (сопроцессом). По одной части канала GAWK отправляет сформированные в программе запросы, а по другой части получает от серверного процесса ответы.
В общем случае синтаксис выглядит так
Собственно оператор |& здесь ключевой. Один канал (в сторону сервера) пробрасывается между командой Awk (например print ) и серверным процессом, а второй – между серверным процессом и командой getline , на которую будут приходить ответы сервера. Как и в случае с pipe-файлами, переменные NR и FNR не будут изменяться.
Покажем эту технику на примере обращения к программе sort .
Здесь Awk процесс открывает два канала и запускает процесс sort . Внутренне канал в сторону сервера именуется «to» , а канал в сторону GAWK – «from» . Используя эти идентификаторы, мы можем явно закрывать каналы, когда это нужно. Без их указания, оба канала будут закрыты.
Процесс sort работает, пока есть что читать из канала в его сторону. Когда мы закрываем канал «to» , то посылаем sort символ EOF , который и завершит сопроцесс. Пока EOF не посылается, sort накапливает данные для сортировки. Затем после EOF отправляет их же, но отсортированными и завершается. Awk просто печатает ответы и закрывает канал «from» . Закрывать каналы явно является правилом хорошего тона, которым в больших программах пренебрегать нельзя.
Практические примеры [ править ]
Awk имеет множество применений, и описать все невозможно ни в одной книге. Чем больше вы используете эту утилиту, тем шире ваш взгляд. В этом разделе мы постарались разместить наиболее интересные примеры, рожденные практикой, которые помогут вам проникнуться общей идеей поточной обработки и начать писать собственные программы.
Программы-однострочники [ править ]
Удаление повторов [ править ]
Следующий вызов похож на утилиту uniq и фильтрует повторы во входящем потоке.
В этом примере используется блок с условием. Когда программа обращается к массиву первый раз и пытается применить на результате отрицание ( !seen[$0] ), то получает ИСТИНУ, потому что элемента еще не существует (пустая строка это ЛОЖЬ, а ее инверсия это ИСТИНА). Побочным эффектом этой операции является создания в массиве seen элемента с ключом $0 , а его значение будет увеличено на единицу (счетчик повторений данного фрагмента).
Так как условие ИСТИНА, то входящий фрагмент будет напечатан. В следующий раз, когда встретиться тот же фрагмент, обращение к массиву вернет его счетчик. Так как ненулевое число это ИСТИНА, а инверсия это ЛОЖЬ, то фрагмент не будет напечатан.
Другими словами, создается иллюзия фильтрации входного потока от повторов. В дальнейшем вы можете заложить вывод счетчиков повторов, если вам будет нужно.
Работа с файлами, как с множествами [ править ]
Некоторые операции с множествами легко реализуются через Awk, в частности такие как объединение, пересечение и разница.
Реализация объединения вам должна быть понятна по предыдущему примеру.
Пересечение и разница работают похоже:
- Сначала работает первый блок с условием NR == FNR , который помещает в массив lut фрагменты до тех пор, пока не кончится текст первого множества.
- Когда текст первого множества обработан полностью, то счетчик NR не обнуляется и продолжает считать дальше, а счетчик FNR начинает считать для нового файла. Так как условие первого блока теперь всегда будет ЛОЖНЫМ, то он отключается и включается в работу второй блок.
- Второй блок получает фрагменты второго множества и пытается определить были ли они в первом множестве через массив. Соответственно для пересечения печать происходит, если они есть в массиве, а для разницы — если нет.
Используя похожий подход, можно сравнить два файла на одинаковость. Следующая команда возвращает 0, если файлы имеют одинаковое содержимое, и 1 — если разное.
Сначала мы пытаемся разместить все фрагменты двух файлов в массиве, параллельно подсчитывая количество уже размещенных в массиве фрагментов. Если файлы одинаковые, то полученное число должно равняться половине всех обработанных фрагментов (потому что если файлы полностью одинаковые, дубликаты не попадут в массив).
Использование регулярных выражений с условиями [ править ]
Awk можно использовать вместо grep и sed , со всеми преимуществами Awk-языка. Так можно комбинировать регулярные выражения с условиями. Вот лишь несколько примеров коротких, но очень полезных программ.
Программы посложнее [ править ]
Подключение к серверам по протоколам TCP/IP [ править ]
В GAWK поддерживается возможность открытия двустороннего TCP/IP соединения, используя специальный синтаксис:
- net-type — тип сети: inet , inet4 или inet6 .
- protocol — протокол tcp или udp .
- local-port — номер локального порта. Если использовать 0, то система сама выберет среди свободных портов.
- remote-host — адрес удаленного хоста.
- remote-port — порт на удаленном хосте, с которым открывается соединение. Можно использовать 0, если вас не заботит с каким портом связываться.
Давайте попробуем соединиться с публичным сервером по протоколу HTTP и узнать наш внешний IP-адрес. Следующая программа реализует это.
Ниже показан пример работы программы.
CSV-парсер на базе Awk [ править ]
CSV довольно популярный формат для хранения таблиц в текстовом файле из-за его простоты. Напомним как он реализуется.
Каждая строка в файле — это строка таблицы. Для отделения полей в строках таблицы выбирается некоторый разделитель (обычно это символ запятой , ). Если этот разделитель по какой-то причине не удобен, то имеется возможность его изменить, правда читающая программа должна быть уведомлена об этом заранее. Вот собственно и весь формат.
CSV форматы понимают некоторые крупные программы, например можно импортировать CSV-таблицу в Microsoft Excel. На практике может понадобиться реализовывать преобразователи CSV-формата в другой. Данная задача прекрасно решается в Awk. К счастью эта задача уже давно была решена Лорансом Стинсоном, который любезно поделился с миром своими наработками [1] .
Ниже приведен исходный код его парсера. Все комментарии, которые он оставил в исходном файле, были переведены на русский язык.
Пусть у нас есть такой CSV-файл:
Теперь попробуем передать этот файл программе.
Поясним немного назначение параметра trim . Дело в том, что в данной реализации можно указать с какого символа следует значение поля: область внутри кавычек или область между разделителями. В предыдущем примере флаг был поднят, поэтому для Bill Cowl были захвачены только символы между кавычками. Если флаг опустить, то кавычки будут проигнорированы, и будут захвачены все символы между разделителями.
Камень-ножницы-бумага [ править ]
Следующий пример демонстрирует, что на базе Awk может быть создано полноценное интерактивное приложение. Следующий код реализует простенькую игру Камень-ножницы-бумага. Программа просит пользователя выбрать между камнем, ножницами и бумагой, а затем показывает, что выбрала она, используя генератор псевдослучайных чисел. Далее все зависит от того, кто более удачливый.
Программа будет исполняться в цикле до бесконечности, пока пользователь сам не прервет ее.
What are $0 and $1 (dollar sign 0 and 1) in an awk script? [closed]
For example in this awk tutorial there are three examples:
3 Answers 3
In awk, $0 is the whole line of arguments, whereas $1 is just the first argument in a list of arguments separated by spaces. So if I put «Mary had a little lamb» through awk, $1 is «Mary», but $0 is «Mary had a little lamb». The second line is trying to find the substring «Mary» in the whole line given to awk.
Actually example # 2 is using a regex because of this syntax
Which means in your example that if literal text Mary isn’t found anywhere in whole line ( $0 ) then execute awk code.
Whereas $1 == «Mary» is doing direct comparison between literal text Mary and field # 1 ( $1 ).
/mary/ is again using ignre-case regex match on field # 1 and this means if $1 has text mary (ignore-case) then execute rest of the awk code.
From the description in your link (emphasis mine):
The expression is generally either one of the fields or the result of an operation on one of the fields. For example, the following AWK filter rules show, respectively, how to compare the first field to “mary” in a case-insensitive fashion, how to match all records that do not contain “Mary”, and how to do an exact comparison of the first field against “Mary”:
So breaking it down:
Because it’s comparing the first field, it uses $1
how to match all records that do not contain “Mary”,
Since it’s comparing all records, it uses $0
and how to do an exact comparison of the first field against “Mary”:
Язык обработки шаблонов awk
Awk — скриптовый язык построчного разбора и обработки входного потока (например, текстового файла) по заданным шаблонам (регулярным выражениям). Часто используется в сценариях командной строки. С помощью языка awk можно выполнять следующие действия:
- Объявлять переменные для хранения данных.
- Использовать арифметические и строковые операторы для работы с данными.
- Использовать управляющие операторы и циклы, что позволяет реализовать сложные алгоритмы.
- Создавать форматированные отчёты.
Вызов awk выглядит так:
Awk рассматривает входной поток как набор записей. Каждая запись делится на поля. По умолчанию разделителем записей является символ новой строки (то есть записи — это строки), разделителем полей — символ пробела или табуляции. Символы-разделители можно явно определить в программе.
- -F fs — позволяет указать символ-разделитель для полей в записи.
- -f file — указывает имя файла, из которого нужно прочесть awk-скрипт.
- -v var=value — позволяет объявить переменную и задать её значение по умолчанию.
- -mf N — задаёт максимальное число полей для обработки в файле данных.
- -mr N — задаёт максимальный размер записи в файле данных.
Чтение awk-скриптов из командной строки
Скрипты awk, которые можно писать прямо в командной строке, оформляются в виде текстов команд, заключённых в фигурные скобки. Кроме того, текст скрипта нужно заключить в одинарные кавычки:
При вызове не указан файл с данными, поэтому awk ожидает поступления данных из STDIN . Чтобы завершить работу awk, нужно передать ему символ конца файла, воспользовавшись сочетанием клавиш CTRL+D .
Позиционные переменные, хранящие данные полей
Одна из основных функций awk заключается в возможности манипулировать данными в текстовых файлах. Делается это путём автоматического назначения переменной каждому элементу в строке. По умолчанию awk назначает следующие переменные каждому полю данных, обнаруженному им в записи:
- $0 — представляет всю строку текста (запись)
- $1 — первое поле в записи (строке)
- $2 — второе поле в записи (строке)
- и так далее
К переменной $n можно обратиться не только с помощью номера 0, 1, 2. Но и использовать переменную или выражение:
Поля выделяются из текста с использованием символа-разделителя. По умолчанию — это пробел или символ табуляции.
Если в качестве разделителя полей используется что-то, отличающееся от пробела или табуляции:
Использование нескольких команд
Awk позволяет обрабатывать данные с использованием многострочных скриптов. Чтобы передать awk многострочную команду, нужно разделить её части точкой с запятой:
Первая команда записывает новое значение в переменную $4 , а вторая выводит на экран всю строку.
Чтение скрипта awk из файла
Awk позволяет хранить скрипты в файлах и ссылаться на них, используя ключ -f . Подготовим файл user-home.awk , в который запишем следующее:
Вызовем awk, указав этот файл в качестве источника команд:
В файле скрипта может содержаться множество команд, при этом каждую из них достаточно записывать с новой строки, ставить после каждой точку с запятой не требуется:
Выполнение команд до начала обработки данных
Иногда нужно выполнить какие-то действия до того, как скрипт начнёт обработку записей из входного потока. Для этого можно воспользоваться ключевым словом BEGIN . Команды, которые следуют за BEGIN , будут исполнены до начала обработки данных.
Выполнение команд после окончания обработки данных
Ключевое слово END позволяет задавать команды, которые надо выполнить после окончания обработки данных:
Напишем скрипт следующего содержания и сохраним его в файле begin-end.awk :
Тут, в блоке BEGIN , создаётся заголовок табличного отчёта. В этом же разделе мы указываем символ-разделитель. После окончания обработки файла, благодаря блоку END , создается подвал отчета. Запустим скрипт:
Основные встроенные переменные
Кроме позиционных переменных $1 , $2 , $3 , которые позволяют извлекать значения полей, есть еще множество других. Вот некоторые из наиболее часто используемых:
- FIELDWIDTHS — разделённый пробелами список чисел, определяющий точную ширину каждого поля данных с учётом разделителей полей.
- FS — переменная, позволяющая задавать символ-разделитель полей.
- RS — переменная, позволяющая задавать символ-разделитель записей.
- OFS — разделитель полей на выводе awk-скрипта.
- ORS — разделитель записей на выводе awk-скрипта.
По умолчанию переменная OFS настроена на использование пробела. Её можно установить так, как нужно для целей вывода данных:
В некоторых случаях, вместо использования разделителя полей, данные в пределах записей расположены в колонках постоянной ширины. В подобных случаях необходимо задать переменную FIELDWIDTHS таким образом, чтобы её содержимое соответствовало особенностям представления данных.
При установленной переменной FIELDWIDTHS awk будет игнорировать переменную FS и находить поля данных в соответствии со сведениями об их ширине, заданными в FIELDWIDTHS .
Переменные RS и ORS задают порядок обработки записей. По умолчанию RS и ORS установлены на символ перевода строки. Это означает, что awk воспринимает каждую новую строку текста как новую запись и выводит каждую запись с новой строки.
Иногда случается так, что поля в потоке данных распределены по нескольким строкам:
Здесь в переменную FS мы записываем символ перевода строки. Это укажет awk на то, что каждая строка в потоке данных является отдельным полем. Кроме того, в переменную RS мы записываем пустую строку. Потому что в файле users.txt блоки данных о разных людях разделены пустой строкой. В результате awk будет считать пустые строки разделителями записей.
Дополнительные встроенные переменные
Помимо встроенных переменных, о которых мы уже говорили, существуют и другие:
- ARGC — количество аргументов командной строки.
- ARGV — массив с аргументами командной строки.
- ARGIND — индекс текущего обрабатываемого файла в массиве ARGV .
- ENVIRON — ассоциативный массив с переменными окружения и их значениями.
- ERRNO — код системной ошибки, которая может возникнуть при чтении или закрытии входных файлов.
- FILENAME — имя входного файла с данными.
- IGNORECASE — если эта переменная установлена в ненулевое значение, при обработке игнорируется регистр символов.
- FNR — номер текущей записи в файле данных.
- NF — общее число полей данных в текущей записи.
- NR — общее число обработанных записей.
Переменные ARGC и ARGV позволяют работать с аргументами командной строки. При этом скрипт, переданный awk, не попадает в массив аргументов ARGV :
Переменная ENVIRON представляет собой ассоциативный массив с переменными среды:
Переменные среды можно использовать и без обращения к ENVIRON :
Переменная NF позволяет обращаться к последнему полю данных в записи, не зная его точной позиции:
Эта переменная содержит числовой индекс последнего поля данных в записи. Обратиться к данному полю можно, поместив перед NF знак $ .
Переменные FNR и NR , хотя и могут показаться похожими, на самом деле различаются. Так, переменная FNR хранит число записей, обработанных в текущем файле. Переменная NR хранит общее число обработанных записей:
Каждая переменная или поле могут потенциально быть строкой или числом. Awk рассматривает переменную как строковую, пока не возникает необходимость выполнить операции сложения или конкатенации. Если к числу прибавляется строка, то строка автоматически преобразуется в число. Если к строке «прицепляется» число, то число преобразуется в строку.
Условный оператор if
Однострочный вариант оператора:
Если нужно выполнить в блоке if несколько операторов, их нужно заключить в фигурные скобки:
условный оператор if может содержать блок else :
Цикл while
Цикл while позволяет перебирать наборы данных, проверяя условие, которое остановит цикл.
В циклах while можно использовать команды break и continue . Первая позволяет досрочно завершить цикл и приступить к выполнению команд, расположенных после него. Вторая позволяет, не завершая до конца текущую итерацию, перейти к следующей.
Цикл for
Решим задачу расчёта среднего значения числовых полей с использованием цикла for :
Массивы
У awk есть ассоциативные массивы — в качестве индекса можно использовать строку или число. Нет необходимости заранее объявлять размер массива — массив может быть изменен во время выполнения.
Пример использования массива:
Удаление элемента массива:
Форматированный вывод данных
Команда printf позволяет выводить форматированные данные. Она даёт возможность настраивать внешний вид выводимых данных благодаря использованию шаблонов, в которых могут содержаться текстовые данные и спецификаторы форматирования.
Спецификатор форматирования — это специальный символ, который задаёт тип выводимых данных и то, как именно их нужно выводить. Awk использует спецификаторы форматирования как указатели мест вставки данных из переменных, передаваемых printf . Первый спецификатор соответствует первой переменной, второй спецификатор — второй, и так далее.
- %c — воспринимает переданное ему число как код ASCII-символа и выводит этот символ.
- %d — выводит десятичное целое число.
- %i — то же самое, что и d .
- %e — выводит число в экспоненциальной форме.
- %f — выводит число с плавающей запятой.
- %g — выводит число либо в экспоненциальной записи, либо в формате с плавающей запятой, в зависимости от того, как получается короче.
- %o — выводит восьмеричное представление числа.
- %s — выводит текстовую строку.
Встроенные функции
При работе с awk программисту доступны встроенные функции. В частности, это математические и строковые функции, функции для работы со временем.
Математические функции
- cos(x) — косинус x (x выражено в радианах).
- sin(x) — синус x (x выражено в радианах).
- exp(x) — экспоненциальная функция.
- int(x) — возвращает целую часть аргумента.
- log(x) — натуральный логарифм.
- rand() — возвращает случайное число с плавающей запятой в диапазоне от 0 до 1.
- sqrt(x) — квадратный корень из x.
Строковые функции
- length([arg]) — возвращает длину arg ; если arg не указан, то выдает длину текущей строки.
- match(string,pattern) — возвращает позицию вхождения шаблона pattern в строку string ; или 0 , если совпадение не найдено.
- index(string,needle) — возвращает начальную позицию вхождения подстроки needle в строку string ; если needle в string не содержится, возвращает 0 .
- split(string,array[,sep]) — помещает поля строки string в массив array и возвращает число заполненных элементов массива; если указан sep , то при анализе строки он понимается как разделитель.
- sub(replace,pattern[,string]) — заменяет в строке string первое вхождение шаблона pattern на строку replace ; в случае отсутствия аргумента string , применяется к текущей записи.
- gsub(replace,pattern[,string]) — аналогична sub() , но заменяет все вхождения.
- substr(string,start,length) — возвращает подстроку строки string , начиная с позиции start , длиной length символов.
- tolower() — перевод в нижний регистр.
- toupper() — перевод в верхний регистр.
Вот как пользоваться этими функциями:
Функция system
Функция system(«command») выполняет команду command и возвращает состояние выполненной команды.
Пользовательские функции
При необходимости можно создавать собственные функции awk. Для возвращения значения из функции можно использовать оператор return .
Шаблоны
В общем случае программа awk имеет вид:
Каждая запись поочерёдно сравнивается со всеми шаблонами , и каждый раз когда найдено соответствие, выполняется указанное действие . Если шаблон не указан, то действие выполняется для любой записи. Если не указано действие , то запись выводится. Специальные шаблоны BEGIN и END позволяют получить управление перед чтением первой входной строки и после прочтения последней входной строки, соответственно.
В предпоследнем примере $3
/903/ означает, что третье поле содержит строку 903 . В последнем примере $3 !
/903/ все наоборот — третье поле не должно содержать строку 903 .
В шаблонах можно использовать регулярные выражения:
Шаблоны в awk это не просто строка или регулярное выражение. Они могут быть произвольными комбинациями относительных выражений (больше, меньше, равно, не равно, …) и регулярных выражений с использованием ! , || , && и круглых скобок:
В случае использования относительных выражений < , <= , == , != , >= , > происходит сравнение чисел, если оба операнда — числа. В противном случае сравниваются строки.