есть массивы. В качестве простого примера приведем программу
awk
, в которой каждая входная строка заносится в отдельный элемент массива, индексируемого номером строки, а затем они печатаются в обратном порядке:
$ cat backwards
# backwards: print input in backward line order
awk ' { line[NR] = $0 }
END { for (i = NR; i > 0; i--) print line[i] } ' $*
$
Заметьте, что подобно переменным, массивы не нужно описывать; размер массива ограничен только объемом памяти, доступным на вашей машине. Конечно, если очень большой файл заносится в массив, в конце концов, это может привести к исчерпанию ресурсов
памяти. Для печати конца большого файла в обратном порядке следует обратиться за помощью к команде
tail
:
$ tail -5 /usr/dict/web2 | backwards
zymurgy
zymotically
zymotic
zymosthenic
zymosis
$
Команда
tail
использует возможности файловой системы — операцию "поиск" (seeking), позволяющую перейти к концу файла без чтения всей предшествующей информации. Подробнее эта операция будет рассмотрена при обсуждении функции
lseek
в гл. 7. (В нашей команде
tail
есть флаг
– r
, который определяет печать строк в обратном порядке, заменяя команду
backwards
).
При обычной обработке входная строка разбивается на поля. Эту операцию можно выполнить с помощью встроенной функции
split
над любой строкой:
n = split(s, arr, sep)
Строка
s
разбивается на поля, записываемые в элементы массива
arr
от 1 до
n
. Используется символ разделения полей
sep
, если он задан; в противном случае применяется текущее значение переменной
FS
. Например, обращение
split($0, а, ":")
разбивает входную строку на столбцы, что подходит для обработки файла
/etc/passwd
, поэтому обращение
split("9/29/83", date, "/")
разбивает дату по символам дробной черты.
$ sed 1q /etc/passwd | awk '{split($0, a, ":"); print a[1]}'
Чтение следующей входной строки; возвращает 0 в случае конца файла, в противном случае 1
index(s1, s2)
Положение строки
s2
в
s1
; возвращает 0, если строка не входит
int(expr)
Целая часть
expr
; округляет по минимуму
length(s)
Длина строки
s
log(expr)
Натуральный логарифм
expr
sin(expr)
Синус
expr
split(s, a, c)
Разбиение
s
на
а[1]
...
a[n]
по символу
c
; возвращает
n
sprintf(fmt, ...)
Форматирование в соответствии со спецификацией
fmt
substr(s,m,n)
Подстрока в
n
символов строки
s
,
начинающаяся с индекса
m
Таблица 4.5: Встроенные функции
awk
Ассоциативные массивы
Стандартной задачей обработки данных является получение суммарных значений для множества пар имя значение. Иными словами, по входному потоку типа
Susie 400
John 100
Mary 200
Mary 300
John 100
Susie 100
Mary 100
мы хотим получить суммарные значения для каждого имени:
John 200
Mary 600
Susie 500
Программа
awk
предлагает изящное решение этой задачи — с помощью ассоциативных массивов. Хотя обычно мы представляем себе индекс массива как целое число, в
awk
любое значение можно использовать в качестве индекса. Поэтому
{sum[$1] += $2}
END {for (name in sum) print name sum [name]}
задает всю программу подсчета n печати сумм для пар имя значение независимо от порядка следования этих пар. Каждое имя (
$1
) служит индексом в массиве
sum
; в конце применена специальная форма цикла
for
для перебора всех элементов
sum
и их печати. Синтаксис этого варианта цикла
for
таков:
for (перем in массив)
оператор
Хотя он может показаться вам искусственным, как цикл
for
языка
shell
, они никак не связаны. Цикл охватывает индексы массива, а не его элементы, устанавливая значение "перем" равным каждому индексу поочередно. Однако порядок появления индексов непредсказуем, поэтому может возникнуть необходимость в их сортировке. В приведенном примере выходной поток можно по конвейеру передать команде
sort
, чтобы имена шли в порядке убывания значений:
$ awk '...' | sort +1nr
Реализация ассоциативной памяти предполагает хэширование, чтобы доступ к одному элементу занимал столько же времени, сколько и к любому другому, и чтобы это время не зависело (по крайней мере для массивов средних размеров) от числа элементов в массиве.
Использование ассоциативных массивов эффективно для вычислительных задач, таких, как подсчет частоты появления слов во входном потоке:
$ cat wordfreq
awk ' { for (i = 1; i <= NF; i++) num[$i]++ }
END {for (word in num) print word, num[word] }
' $*
$ wordfreq ch4.* | sort +1 -nr | sed 20q | 4
the 372 .CW 345 of 220 is 185
to 175 a 167 in 109 and 100
.PI 94 .P2 94 .PP 90 $ 87
awk 87 sed 83 that 76 for 75
The 63 are 61 line 55 print 52
$
В первом цикле
for
выбирается каждое слово из входной строки и заполняется массив
num
, индексируемый словами. (Не путайте
$i
, обозначающее в
awk
i-е поле входной строки, с переменными языка
shell
.) После того как файл будет прочитан, во втором цикле
for
печатаются в произвольном порядке слова и частота их появления.
Упражнение 4.9
В результат действия команды
wordfreq
попали команды форматирования типа
.CW
, которые применяются для печати слов определенным шрифтом. Как избавиться от таких ненастоящих слов? Как бы вы использовали команду
tr
, чтобы программа
wordfreq
работала правильно, независимо от того, прописные или строчные буквы задействованы во входном потоке? Сравните реализацию и скорость выполнения программы
wordfreq
, конвейера из разд. 4.2 и предлагаемого ниже решения.