Мы могли бы определить программу
ind
(рассматривавшуюся в начале главы) следующим образом:
$ awk '{printf "\t%s\n", $0}' $*
Здесь выдается символ табуляции (
\t
) и входная строка.
Шаблоны
Предположим, что вы хотите найти в файле
/etc/passwd
пользователей, не имеющих пароля. Зашифрованный пароль находится во втором поле, поэтому программа состоит из одного шаблона:
$ awk -F: '$2 == ""' /etc/passwd
Шаблон проверяет, является ли второе поле пустой строкой (операция
==
— это проверка на равенство).
Такой шаблон можно задать различными способами:
$2==""
Второе поле пусто
$2~/^$/
Второе поле соответствует пустой строке
$2!~/./
Второе поле не содержит ни одного символа
length($2) == 0
Длина второго поля равна нулю
Символ
~
обозначает соответствие регулярному выражению, а символ
!
— отсутствие соответствия. Само регулярное выражение заключено в символы дробной черты.
Встроенная функция
length
программы awk вычисляет длину строки символов. Шаблону может предшествовать символ ! для отрицания его, например,
!($2=="")
Операция
!
подобна такой же операции в языке Си, но в редакторе
sed
эта операция следует за шаблоном.
Наиболее типичное использование шаблонов в программе
awk
сводится к задачам простой проверки данных. Большинство из них немногим сложнее, чем поиск строк, не удовлетворяющих какому-то критерию; если нет выходного потока, то считается, что данные удовлетворяют соответствующему критерию (по принципу "отсутствие новостей — хорошая новость"). Например, в следующем шаблоне проверяется с помощью операции
%
, вычисляющей остаток от деления, четно или не четно число полей в каждой входной строке:
$ NF % 2 != 0 # напечатать, если нечетное число полей
Другой шаблон выдает исключительно длинные строки, используя встроенную функцию
length
:
length ($0) >72 # напечатать, если слишком длинная строка
В программе
awk
используется то же соглашение о комментарии, что и в интерпретаторе: символ
#
отмечает начало комментария.
Можно сделать выходной поток более информативным, снабдив его предупреждающим сообщением и частью слишком длинной строки, используя для этого встроенную функцию
substr
:
length($0) > 72 {print "Строка", NR, "длинная" : substr($0, 1, 60)}
Функция
substr(s, m, n)
выделяет подстроку из строки s, начинающуюся с символа с номером
m
и длиной в
n
символов. (Символы в строке нумеруются с 1.) Если
n
отсутствует, то берется подстрока от
m
до конца строки. Эту функцию можно использовать для выделения полей с фиксированным положением, например выделить время в часах и минутах из результата выполнения команды
date
:
$ date
Thu Sep 29 12:17:01 EDT 1983
$ date | awk '{print substr($4, 1, 5) }'
12:17
$
Упражнение 4.7
Сколько различных программ
awk
вы можете составить для переписи входного потока в выходной, как это делает команда
cat
? Какая из них самая короткая?
Программа
awk
имеет два специальных шаблона
BEGIN
и
END
. Действия, соответствующие
BEGIN
, выполняются прежде, чем читается первая входная строка; можно использовать этот шаблон для инициации переменных, печати заголовков или для установки символа разделителя полей, присваивая его переменной
FS
.
$ awk 'BEGIN { FS = ":" }
> $2 == "" ' /etc/paswd
$
Результата нет: все работают с паролями
Действия, указанные в шаблоне
END
, выполняются после обработки последней входной строки:
$ awk 'END {print NR}'...
Здесь печатается число строк входного потока.
Арифметика и переменные
До сих пор в примерах выполнялись только простые операции с текстом. Достоинством программы
awk
является ее возможность попутно проводить вычисления над входными данными: что-нибудь подсчитать, вычислить суммы и средние значения и т.п. Типичный пример таких вычислений — подсчет суммы столбца чисел. Так, следующая команда складывает все числа первого столбца
{s=s+$1}
END {print s}
Поскольку число значений доступно с помощью переменной
NR
, изменив последнюю строку на
END {print s, s/NR}
мы получим и сумму, и среднее значение.
Этот пример показывает, как используются переменные в awk. Переменная
s
не является встроенной, она определяется самим фактом использования. По умолчанию переменные инициируются нулем, так что, как правило, не нужно беспокоиться об их инициации.
В программе
awk
есть такие же сокращенные формы арифметических операторов, как и в языке Си, поэтому естественная запись примера имела бы вид:
{s+=$1}
END {print}
Запись
s+=$1
равноценна записи
s=s+$1
, но более компактна. Можно обобщить пример по подсчету входных строк:
{ nc+=length($0) +1 # число символов, +1 для \n
nw += NF # число слов
}
END {print NR, nw, nc }
Здесь подсчитывается число строк, слов и символов входного потока, т.е. выполняются те же действия, что и по команде
wc
(хотя она и не разбивает общую сумму по файлам).