Введение
В данном случае название статьи слегка вводит нас в заблуждение, потому что awk — это больше, чем команда. Это язык программирования со своими собственными правилами. Вы можете писать на нем скрипты для выполнения сложных операций, либо можете просто использовать из командной строки. Его название составлено из заглавных букв Aho, Weinberger и Kernighan (да, тот самый Brian Kernighan), авторов языка, история которого началась в 1977 году. Если вы учитесь или учились программированию на C, то увидите многие похожие концепции в awk. Вам понадобятся определенные знания командной оболочки Linux и, возможно, базовые навыки написания скриптов, что, впрочем, необязательно, так как мы постараемся изложить материал как можно проще. Большое спасибо Arnold Robbins за его работу над awk.
Функции, определяемые программистом
awk
function FunctionName(a, b, ..., z) { ... тело функции ... }
awk
Тело функции представляет собой набор awk-команд, применяемых к
списку параметров заголовка функции.
Функция может быть »реккурсивной», вызывая самое себя.
Количество аргументов в фактическом вызове функции может отличаться
от количества аргументов в описании.
»Лишние» переменные получают в качестве значений пустую строку
.
При вызове функции в awk-программе не должно быть никаких символов
( в том числе и пробельных ) между
именем функции и списком аргументов в скобках.
В awk используется механизм передачи параметров »по значению»,
так что всякие изменения, происходящие с аргументами в функции,
не изменяют значений этих переменных вне функции.
Однако, если аргументом функции является массив,
значения его элементов могут быть изменены внутри тела функции.
Ввод данных в awk
awk
В строке вызова awk можно указать несколько файлов данных,
awk будет последовательно их обрабатывать.
Имя текущего файла ввода
доступно как значение переменной FILENAME.
Эти базовые возможности awk по вводу данных могут быть существенно
расширены использованием функции (или команды) getline.
Использованная без аргументов, getline просто читает очередную
запись из текущего файла ввода. Это позволяет, например, специальным
образом обработать следующую строку, если вы, например, обнаружили во
входных данных сообщение типа:
TOP SECRET! NEXT LINE TO BE BURNED BEFORE READING!
После выполнения команды getline awk продолжает нормальный цикл
своей деятельности, применяя к этой записи последующие пары
образец — действие.
Команда getline может быть использована для ввода значения
строковой переменной:
getline message
Никаких побочных эффектов при этом не происходит, за исключением того,
что изменяется счетчик записей NR.
Команда getline может читать из поименованного файла, используя
технику перенаправления:
getline < file
Выражение file может быть произвольным строковым выражением,
которые
интерпретируются как имя файла в данной операционной системе.
Если getline не встретила каких-либо проблем при исполнении, она
возвращает значение 1.
Если при исполнении обнаружен конец файла,
то возвращается ,
если обнаружена ошибка, то возвращается -1.
В последнем случае причину ошибки можно найти, проанализировав
значение переменной ERRNO.
Синтаксис команды awk
Сначала надо понять как работает утилита. Она читает документ по одной строке за раз, выполняет указанные вами действия и выводит результат на стандартный вывод. Одна из самых частых задач, для которых используется awk — это выборка одной из колонок. Все параметры awk находятся в кавычках, а действие, которое надо выполнить — в фигурных скобках. Вот основной её синтаксис:
$ awk опции ‘ условие ‘
$ awk опции ‘ условие условие ‘
С помощью действия можно выполнять преобразования с обрабатываемой строкой. Об этом мы поговорим позже, а сейчас давайте рассмотрим опции утилиты:
- -F, —field-separator — разделитель полей, используется для разбиения текста на колонки;
- -f, —file — прочитать данные не из стандартного вывода, а из файла;
- -v, —assign — присвоить значение переменной, например foo=bar;
- -b, —characters-as-bytes — считать все символы однобайтовыми;
- -d, —dump-variables — вывести значения всех переменных awk по умолчанию;
- -D, —debug — режим отладки, позволяет вводить команды интерактивно с клавиатуры;
- -e, —source — выполнить указанный код на языке awk;
- -o, —pretty-print — вывести результат работы программы в файл;
- -V, —version — вывести версию утилиты.
Это далеко не все опции awk, однако их вам будет достаточно на первое время. Теперь перечислим несколько функций-действий , которые вы можете использовать:
- print(строка) — вывод чего либо в стандартный поток вывода;
- printf(строка) — форматированный вывод в стандартный поток вывода;
- system(команда) — выполняет команду в системе;
- length(строка) — возвращает длину строки;
- substr(строка, старт, количество) — обрезает строку и возвращает результат;
- tolower(строка) — переводит строку в нижний регистр;
- toupper(строка) — переводить строку в верхний регистр.
Функций намного больше, но чтобы не загромождать статью я привел только те, которые мы будем использовать сегодня, а также ещё несколько для чтобы вы могли оценить масштаб возможностей утилиты.
В функциях-действиях можно использовать различные переменные и операторы, вот несколько из них:
- FNR — номер обрабатываемой строки в файле;
- FS — разделитель полей;
- NF — количество колонок в данной строке;
- NR — общее количество строк в обрабатываемом тексте;
- RS — разделитель строк, по умолчанию символ новой строки;
- $ — ссылка на колонку по номеру.
Кроме этих переменных, есть и другие, а также можно объявлять свои.
Условие позволяет обрабатывать только те строки, в которых содержатся нужные нам данные, его можно использовать в качестве фильтра, как grep. А ещё условие позволяет выполнять определенные блоки кода awk для начала и конца файла, для этого вместо регулярного выражения используйте директивы BEGIN (начало) и END (конец). Там ещё есть очень много всего, но на сегодня пожалуй достаточно. Теперь давайте перейдем к примерам.
Встроенные переменные и расширенный формат awk
Awk использует некоторые встроенные переменные, чтобы определять фрагменты информации при обработке текста.
Список встроенных переменных awk:
- FILENAME: ссылается на текущий входной файл.
- FNR: Ссылается на номер текущей записи относительно текущего вводного файла. Например, если в данный момент открыто два вводных файла, команда выведет номер записи каждого из них.
- FS: текущий разделитель полей, который используется для обозначения каждого поля в записи. По умолчанию установлен пробел.
- NF: количество полей в текущей записию
- NR: номер текущей записи.
- OFS: разделитель полей для выводимых данных. По умолчанию установлен пробел.
- ORS: разделитель записей для выводимых данных. По умолчанию установлен символ новой строки.
- RS: разделитель записей, отделяющий записи во входном файле. По умолчанию это символ новой строки.
Значения этих переменных можно менять в соответствии с потребностями файлов. Обычно это делается во время инициализации обработки awk.
В целом, синтаксис awk немного более сложный, чем кажется сначала. Кроме того, он содержит дополнительные блоки BEGIN и END, которые могут содержать команды, которые нужно выполнить перед или после обработки файла соответственно.
Расширенный синтаксис выглядит примерно так:
Ключевые слова BEGIN и END, на самом деле, просто конкретные совокупности условий, так же, как и параметры поиска. Они совпадают до и после обработки документа.
Это значит, что некоторые переменные блока BEGIN можно изменить. К примеру, файл /etc/passwd разделён с помощью исмволов двоеточия (:), а не пробелов. Чтобы вывести первый столбец этого файла, можно использовать:
Блоки BEGIN и END можно использовать, чтобы получить простую информацию о выведенных полях:
Как видите, воспользовавшись некоторыми функциями awk, можно достаточно гибко отформатировать некоторые параметры.
Оба блока расширения – необязательны. По сути, основные действия тоже необязательны, если другая часть действий уже указана. К примеру, с awk можно работать так:
Синтаксис команды awk
Сначала надо понять как работает утилита. Она читает документ по одной строке за раз, выполняет указанные вами действия и выводит результат на стандартный вывод. Одна из самых частых задач, для которых используется awk — это выборка одной из колонок. Все параметры awk находятся в кавычках, а действие, которое надо выполнить — в фигурных скобках. Вот основной её синтаксис:
$ awk опции ‘условие {действие}’
$ awk опции ‘условие {действие} условие {действие}’
С помощью действия можно выполнять преобразования с обрабатываемой строкой. Об этом мы поговорим позже, а сейчас давайте рассмотрим опции утилиты:
- -F, —field-separator — разделитель полей, используется для разбиения текста на колонки;
- -f, —file — прочитать данные не из стандартного вывода, а из файла;
- -v, —assign — присвоить значение переменной, например foo=bar;
- -b, —characters-as-bytes — считать все символы однобайтовыми;
- -d, —dump-variables — вывести значения всех переменных awk по умолчанию;
- -D, —debug — режим отладки, позволяет вводить команды интерактивно с клавиатуры;
- -e, —source — выполнить указанный код на языке awk;
- -o, —pretty-print — вывести результат работы программы в файл;
- -V, —version — вывести версию утилиты.
Это далеко не все опции awk, однако их вам будет достаточно на первое время. Теперь перечислим несколько функций-действий, которые вы можете использовать:
- print(строка) — вывод чего либо в стандартный поток вывода;
- printf(строка) — форматированный вывод в стандартный поток вывода;
- system(команда) — выполняет команду в системе;
- length(строка) — возвращает длину строки;
- substr(строка, старт, количество) — обрезает строку и возвращает результат;
- tolower(строка) — переводит строку в нижний регистр;
- toupper(строка) — переводить строку в верхний регистр.
Функций намного больше, но чтобы не загромождать статью я привел только те, которые мы будем использовать сегодня, а также ещё несколько для чтобы вы могли оценить масштаб возможностей утилиты.
В функциях-действиях можно использовать различные переменные и операторы, вот несколько из них:
- FNR — номер обрабатываемой строки в файле;
- FS — разделитель полей;
- NF — количество колонок в данной строке;
- NR — общее количество строк в обрабатываемом тексте;
- RS — разделитель строк, по умолчанию символ новой строки;
- $ — ссылка на колонку по номеру.
Кроме этих переменных, есть и другие, а также можно объявлять свои.
Условие позволяет обрабатывать только те строки, в которых содержатся нужные нам данные, его можно использовать в качестве фильтра, как grep. А ещё условие позволяет выполнять определенные блоки кода awk для начала и конца файла, для этого вместо регулярного выражения используйте директивы BEGIN (начало) и END (конец). Там ещё есть очень много всего, но на сегодня пожалуй достаточно. Теперь давайте перейдем к примерам.
В защиту awk
В этой серии статей я собираюсь сделать из читателя искусного программиста на awk. Я согласен, что у awk не самое приятное и модное имя, а GNU-версия awk, названная gawk, звучит откровенно странно. Незнакомые с этим языком программисты, услышав его название, возможно, представят себе мешанину древнего и устаревшего кода, способного довести до умопомрачения даже самого знающего специалиста по UNIX (заставив его восклицать “kill -9!” и беспрестанно бегать за кофе).
Да, у awk отнюдь не замечательное имя. Но это замечательный язык. Awk создан для обработки текста и создания отчетов, но у него много хорошо проработанных функций, дающих возможность серьезного программирования. При этом, в отличие от некоторых других языков, синтаксис awk привычен и заимствует лучшее из таких языков, как C, python и bash (хотя формально awk был создан до python и bash). Awk — один из тех языков, которые, будучи один раз выучены, становятся ключевой частью стратегического арсенала программиста.
Contents
- Contents
Что такое строковый процессор awk
Подобные задания с одной стороны могут быть слишком трудоемки
для обычного текстового редактора,
так как могут требовать просмотра тысяч строк текста,
а с другой стороны — неунифицируемыми, так как информация
не структурирована в том виде, как это имеет место в базах
данных или электронных таблицах.
Для эффективной работы в этой »серой» области давно предложен и с успехом
применяется строковый процессор awk.
Это процессор рассматривает входной поток данных
как состоящий из записей,
разделенными специальными символами (RS).
По умолчанию таким символом является символ перехода
на новую строку (»).
Запись считается разделенной ( символами FS)
на поля
и строковый процессор awk автоматически выделяет эти поля
и дает возможность производить
с ними различные операции.
По сути дела, это единственное предположение,
которое делает awk относительно структуры входных данных.
Задание процесса обработки некоторого файла с помощью программы awk состоит
в описании действий, которые нужно произвести с записями и полями.
Для этого awk предоставляет в распоряжение программиста
развитый язык программирования, напоминающий популярный язык программирования
C.
Это и не удивительно, так как авторы awk (Альфред В. Ахо ( Alfred V. Aho — a),
Питер Дж. Вайнбергер ( Peter J. Weinberger — w ) и
Брайан У. Кернихан ( Brian W. Kernighan — k)
известны как родоначальники языка C и
операционной системы UNIX.
Входной язык процессора awk является в определенном смысле
»упрощенным» C и не содержит таких типов данных как
указатели и структуры.
Отсутствуют в нем и другие элементы языка C,
такие, напримерa, как команды препроцессора, битовые операции,
локализация переменных и пр.
Однако, в отличие от C
awk предоставляет удобный операционный синтаксис для строковых
операций, автоматическое преобразование строка-число,
автоматический лексический разбор входного потока и т.п.,
что делает его исключительно удобным средством
выполнения простых, но трудоемких операций
c текстовыми файлами больших размеров.
Кроме этого,
awk является интерпретирующим языком, что существенно
упрощает процесс разработки awk-приложений.
Все это делает awk весьма эффективным и полезным инструментом,
умело владеть которым должен каждый грамотный программист.
Структура awk-файла
awk
образец действие.
awkобразцу действие
Программа на awk может содержать определения
функций, которые можно считать также имеющими форму
образец — действие со специальным видом образца.
Подробнее задание функций описано в разделе []
Действие описывается операторами языка awk,
которые подробнее рассматриваются далее.
Чтобы отделить условие от действия,
последнее обычно заключается в фигурные скобки .
Ислючение составляет лишь случай пустого действия,
которое в этом случае приводит к выводу на печать входной записи.
Примеры решения задач с помощью awk
Задача 1.
Есть файл вот с таким содержимым:
ОРГАНИЗАЦИЯ| НАЧИСЛЕНИЕ| НАЧИСЛЕНИЕ| ОРГАНИЗАЦИЯ НАЧИСЛЕНИЕ| НАЧИСЛЕНИЕ| НАЧИСЛЕНИЕ| НАЧИСЛЕНИЕ| НАЧИСЛЕНИЕ| НАЧИСЛЕНИЕ| НАЧИСЛЕНИЕ| НАЧИСЛЕНИЕ| НАЧИСЛЕНИЕ| ОРГАНИЗАЦИЯ НАЧИСЛЕНИЕ| НАЧИСЛЕНИЕ| ОРГАНИЗАЦИЯ НАЧИСЛЕНИЕ| НАЧИСЛЕНИЕ| НАЧИСЛЕНИЕ| НАЧИСЛЕНИЕ| НАЧИСЛЕНИЕ| НАЧИСЛЕНИЕ| НАЧИСЛЕНИЕ| ОРГАНИЗАЦИЯ НАЧИСЛЕНИЕ| НАЧИСЛЕНИЕ| ОРГАНИЗАЦИЯ НАЧИСЛЕНИЕ| НАЧИСЛЕНИЕ| НАЧИСЛЕНИЕ|
Таких строк может быть любое количество, как в меньшую, так и в большую сторону. После слов «ОРГАНИЗАЦИЯ» строка продолжается наименованием и реквизитами организации.
После слов «НАЧИСЛЕНИЕ» строка продолжается суммой начисления и прочими данными.
Разделителем является труба: |
Задача такая — с помощью awk найти слово «ОРГАНИЗАЦИЯ», скопировать содержимое этой строки и привязанные к ней строки «НАЧИСЛЕНИЕ».
Т.е. первую строку «ОРГАНИЗАЦИЯ» и две строки ниже со словом «НАЧИСЛЕНИЕ». Вывести эти 3 строки в отдельный файл. Количество строк может быть любым.
Найти следующие строки по такому же принципу и опять в отдельный файл.
По этому примеру должно быть 6 файлов и каждый со своей организацией и начислениями.
Решение 1. Работает в awk под Linux
awk -v y=0 '/ОРГАНИЗАЦИЯ/{if(x) print x>y; x=$0 ;y++} !/ОРГАНИЗАЦИЯ/{x=x"\r\n"$0} END {print x>y}' test.txt
Решение 2. Работает в awk под Windows
awk.exe -v y=0 "/ОРГАНИЗАЦИЯ/{y++; print $0>y\".txt\";} !/ОРГАНИЗАЦИЯ/{print $0>y\".txt\"}" test.txt
Входные данные в файле test.txt. Тестировалась в cmd (а не в PowerShell).
Скриншот:
Содержимое первых четырёх файлов:
Задача 2.
Как добавить в существующий файл строку с нужными мне данными?
Т.е. есть файл, например, с 6 строками и я хочу добавить сроку перед 1 строкой или добавить строку во все .txt файлы в определённой папке.
Решение 1. Работает в awk под Linux
Итак, следующий пример считывает все файлы из текущего каталога (*). Если нужно, чтобы считывал только файлы с определённым разрешением, то вместо звёздочки можно записать, например, так *.txt.
Для каждого обрабатываемого файла в самое начало добавляется строка «СТРОКА» — замените на нужную. Новые файлы сохраняются в эту же папку, с такими же именами, но перед именем добавляется префикс «new_» — при желании, его можно удалить или заменить на другой (в двух местах команды!).
Собственно команда:
awk -v 'OLD_FILENAME=""' '{if(OLD_FILENAME!=FILENAME) print "СТРОКА" > "new_"FILENAME; OLD_FILENAME=FILENAME} {print > "new_"FILENAME;}' *
Логика работы:
- при запуске awk инициализируется переменная OLD_FILENAME — которой в качестве значения присваивается пустая строка
- при обработке каждой строки, значение переменной OLD_FILENAME сравнивается со значением встроенной переменной FILENAME в которой (сюрприз!) содержится имя текущего файла. Если эти значения НЕ равны, то в файлы печатается строка «СТРОКА» И переменной OLD_FILENAME присваивается значение, которое содержит FILENAME.
- следующая часть {print > «new_»FILENAME;} просто печатает очередную строку в новый файл
- при последующих прохождениях (вторая строка, третья строка и т. д.) значения OLD_FILENAME и FILENAME будут одинаковыми. Вплоть до момента, пока обрабатываемый файл не сменится на новый. В этот момент OLD_FILENAME и FILENAME становятся не равны и в новый файл первой печатается строка «СТРОКА» и переменной OLD_FILENAME присваивается значение FILENAME
- всё продолжается, пока все строки во всех файлах не кончатся
Решение 2. Работает в awk под Windows
Для Windows (cmd, а не PowerShell):
awk.exe -v OLD_FILENAME="" "{if(OLD_FILENAME!=FILENAME) print \"СТРОКА\" > \"new_\"FILENAME; OLD_FILENAME=FILENAME} {print > \"new_\"FILENAME;}" *.txt
Как разделить поля
После прочтения записи одна из предопределенных переменных FS, FIELDWIDTHS или FPAT будет использоваться для разделения поля. После того, как разделение завершено, введите основной сегмент кода (таким образом, установка FS в main не влияет на запись, которая была прочитана на этот раз, но повлияет на следующее чтение).
Метод разделения поля (1): FS или -F
или: Разделитель полей
- Когда FS — один символ, символ является разделителем поля
- Когда FS состоит из нескольких символов, шаблон регулярного выражения используется в качестве разделителя полей
- Специальный также стандартная ситуация FS,Когда FS — это один пробел, в качестве разделителей полей будут использоваться непрерывные пробелы (пробелы, табуляции, разрывы строк).
- Специально, когда в ФС есть пустая строка «», каждый символ будет разделен, то есть каждый символ используется в качестве поля
- Установите предопределенную переменную IGNORECASE в ненулевое значение. Когда регулярное сопоставление означает, что игнорируется регистр (влияет только на регистр, поэтому FS не действует, когда это одно слово)
- Если разделитель, указанный в FS, не может быть найден в записи (например, установите FS в «\ n»), вся запись используется в качестве поля, то естьс участиемравный
Метод разделения поля (2): ОБЛАСТИ ПОЛЯ
Укажите предопределенную переменную FIELDWIDTHS, чтобы разделить поле по ширине символа, что является расширенной функцией gawk. Очень полезно при работе с пропущенными полями.
использование:
Пример 1:
Пример 2. Обработка данных, отсутствующих в определенных полях.
Если сегментация полей выполняется в соответствии с обычной FS, трудно обрабатывать строки с пропущенными полями и строки без пропущенных полей равномерно, но очень удобно использовать FIELDWIDTHS.
Предположим, текстовое содержимое файла a.txt выглядит следующим образом:
Поскольку некоторые поля электронной почты являются пустыми полями, неудобно напрямую разделять поля с помощью FS. FIELDWIDTHS могут быть использованы.
Метод разделения поля (3): FPAT
FS должен указать разделитель полей для получения части, отличной от разделителя, в качестве поля.
FPAT должен получить соответствующую часть символа в виде поля. Это расширенная функция, предоставляемая gawk.
FPAT сопоставляет запись глобально в соответствии с заданной регулярностью, а затем составляет все части, которые успешно совпадают, Не изменится。
- После установки FS или FPAT эта переменная будет недействительной
FPAT часто используется в сценариях, где в поле включен разделитель полей. Например, строка данных в файле CSV выглядит следующим образом:
Запятая разделяет каждое поле, но двойные кавычки окружают поле целиком, даже если в нем есть запятая.
В настоящее время удобнее использовать FPAT для разделения полей, чем использовать FS.
Наконец, функция patsplit () такая же, как FPAT.
Циклы
awkCdo-while, while, for
Цикл do-while имеет вид
do { тело цикла } while (условие)
Цикл while имеет вид
while (условие) { тело цикла }
Цикл for имеет две формы в awk: одна — традиционная:
for(инициализация; условия; завершение){ тело цикла }
for(index in list){ тело цикла }
listforlist
В приведенных конструкциях фигурные скобки {} необязательны
если тело цикла представляет собой один оператор.
В отличие от C
язык awk содержит две формы операторов досрочного
прерывания цикла и досрочного перехода к следующему выполнению тела
цикла.
Это связано с тем, что сама конструкция awk подразумевает
неявный внешний цикл: чтение данных — обработка.
Для досрочного прекращения этого процесса обработки служит оператор
exit.
Он по сути дела эквивалентен прерыванию текущих операций обработки
и чтению признака конца входного файла (EOF для C-патриотов).
Если в awk-программе есть
действия, ассоциированные с образом END, они будут выполнены.
Для досрочного прерывания процесса обработки текущей записи
(и запуска новой операции чтения — обработки) служит оператор next.
Для управления циклами, в явном виде содержащимися в
awk-программах, служат операторы break, continue.
Оператор break
прекращает
выполнение цикла, continue запускает новое исполнение тела цикла.
К операторам управления последовательностью операций с некоторой
натяжкой можно отнести и return — оператор возврата из функции,
описанной программистом.
Оператор
return выражение
позволяет сообщить вызывающей программе
в качестве значения функции вычисленные значения выражения.
Массивы
awkCFortranAawk
Например, если перед вами стоит задача разобрать текстовый файл-таблицу,
в которой, скажем, в первом столбце находится фамилия работника,
получившего вознаграждение, обьем которого находится в седьмом столбце
таблицы и трубуется определить суммарное вознаграждение для каждого
из работающих, awk решает эту задачу с потрясающей легкостью:
{ payoff += $">7 }
и проблема остается лишь в выводе значений массива payoff
по завершению работы программы.
Конечно, для такого понятия массива трудно ввести предварительное
декларирование и элементы массивов в awk создаются в момент
использования.
По исчерпыванию необходимости в этом элементе,
от него можно в явном виде избавиться при помощи оператора
delete:
delete payoff
и освободить память для более важных дел.
Удалить весь массив payoff можно командой
delete payoff
Определение множества индексов данного массива и
обход элементов массива можно произвести с помощью ассоциативного
цикла, уже упомянутого выше.
Отрывок awk-программы
END { for(name in payoff) printf name, payoff }
Единственным недостатком такого простого решения являетя неопределенный
порядок индексов массива payoff,
в связи с чем их имена почти наверняка не будут выведены в алфавитном
порядке !
Простым выходом из положения будет запуск какой-либо внешней программы
сортировки текстовых файлов, которая достаточно разумна для того,
чтобы знать о существовании русского алфавита.
Еще одним часто встречающимся способом порождения массивов
является использование встроенной функции split.
Эта функция может иметь два или три аргумента и используется для
разбиение какой-либо строки на отдельные части, заполняющие собой
массив.
Например, оператор
n = split("31.12.1999", date, ".")
разобьет строку "31.12.1999" на три части
"31", "12", "1999", используя в качестве разделителя символ "."
и присвоит массиву date значения:
date = "31", date = "12", date = "1999".
n3
Если функция split вызывается с двумя аргуменами,
подразумевается, что символом-разделителем является значение
встроенной переменной awk FS — по умолчанию пробельный символ.
Таким образом
nn = split($0, tokens)
в явном виде выполнит ту работу, что awk делает по умолчанию:
разделит входную строку () на отдельные элементы и запишет их
в массив tokens.
Количество элементов будет запомнено, как значение переменной nn.
Это полностью эквивалентно явному циклу
for(i = 1; i <= NF; i++) tokens = $i nn = NF