Руководство пользователя для GNU Awk

Arnold D. Robbins
перевод Балуева А. Н.

11. Массивы в awk

Оглавление

Массив представляет таблицу значений, называемых элементами. Элементы массива различаются своими индексами. Индексы могут быть числами или цепочками. awk поддерживает одно множество имен, которые могут использоваться для именования переменных, массивов и функций (см. главу 13 [Функции, определенные пользователем], стр. 153). Таким образом, нельзя иметь переменную и массив с тем же именем в одной awk-программе.

11.1 Введение в массивы

В начало страницы

Язык awk обеспечивает одноразмерные массивы для хранения групп цепочек или чисел. Каждый массив awk должен иметь имя. Имена массивов имеют тот же синтаксис, что и имена переменных; каждое правильное имя переменной может быть также правильным именем массива. Но нельзя одно и то же имя использовать обоими способами (как массив и как переменную) в одной awk-программе.

Массивы в awk поверхностно сходны с массивами в других языках; но имеются и фундаментальные отличия. В awk не нужно указывать размер массива перед началом его использования. Кроме того, любое число или цепочка могут использоваться как индекс массива, а не только последовательные целые. В большинстве других языков нужно объявлять массив и указывать, сколько элементов, или компонент, он содержит.

В таких языках объявление отводит непрерывный блок памяти для указанного количества элементов. Индекс в массиве обычно обязан быть положительным целым; например, индекс ноль указывает первый элемент массива, который фактически хранится в начале блока памяти.

Индекс один указывает второй элемент, который располагается в памяти сразу после первого элемента, и т.д. Невозможно добавить больше элементов к массиву, потому что он имеет место только для объявленного количества элементов. (Некоторые языки допускают произвольные начальные и конечные индексы, например, `15 .. 27', но размер массива все равно фиксирован при его объявлении.) Непрерывный массив с четырьмя элементами: восемь, "foo", "" и 30: может выглядеть так:

8 "foo" "" 30 значения
0  1    2  3 индексы

Хранятся в памяти только значения; индексы неявно зависят от порядка значений. восемь есть значение с индексом ноль, потому что восемь появляется в позиции с нулем элементов перед ним. Массивы в awk не такие: они ассоциативны. Это значит, что каждый массив есть собрание пар: индекс и соответствующий ему элемент-значение:

Элемент 4 Значение 30 Элемент 2 Значение "foo" Элемент 1 Значение 8 Элемент 3 Значение ""

Мы перечислили пары в случайном порядке, потому что их порядок безразличен. Одно из преимуществ ассоциативных массивов состоит в том, что новые пары можно добавлять в любой момент. Например, предположим, что мы добавляем к предыдущему массиву десятый элемент со значением "number ten". Результат получится такой:

Элемент 10 Значение "number ten" Элемент 4 Значение 30 Элемент 2 Значение "foo" Элемент 1 Значение 8 Элемент 3 Значение "".

Этот массив разрежен, так как некоторые индексы пропущены: имеются элементы 1-4 и 10, но нет элементов 5, 6, 7, 8 и 9.

Другое следствие ассоциативности массивов есть то, что индексы не обязаны быть положительными целыми. Любое число или даже цепочка может быть индексом. Например, вот массив, который переводит слова с английского на французский язык:

Элемент "dog" Значение "chien" Элемент "cat" Значение "chat" Элемент "one" значение "un" Элемент 1 Значение "un"

Здесь мы решили переводить число один сразу и в произносимой и в числовой форме, иллюстрируя тот факт, что один массив может иметь и числа и цепочки в качестве индексов. (Но фактически индексы массивов всегда цепочки; Это в деталях обсуждается в разделе 11.7 [Использование чисел для индексов в массивах], стр. 129.)

Значение IGNORECASE никак не связано с индексацией массивов. Нужно употреблять точно те же самые строковые значения для выборки элементов из массива, которые использовались при его записи в массив.

Когда awk создает для вас массив, например, с помощью встроенной функции split, индексами этого массива становятся последовательные целые, начиная с единицы. (См. раздел 12.3 [Встроенные функции для действий с цепочками], стр. 137.)

11.2 Ссылки на элемент массива

В начало страницы

Основной путь использования массивов состоит в ссылке на их отдельные элементы. Ссылка на массив есть выражение формы:

array[index]

Здесь array означает имя массива. Выражение index есть индекс элемента массива, на который вы ссылаетесь. Значение ссылки на массив есть текущее значение соответствующего элемента. Например, foo[4.3] есть выражение для элемента массива foo с индексом `4.3'. Ссылка на элемент массива, значение которому еще не присвоено, имеет пустое значение "". Это относится также к элементам, которые были вычеркнуты (см. раздел 11.6 [Оператор delete], стр. 128). Такая ссылка автоматически создает этот элемент со значением пустой цепочки. (В некоторых случаях это плохо, потому что зря занимается память внутри awk.)

Вы можете определить, существует ли элемент массива с некоторым индексом, используя выражение: index in array. Это выражение проверяет, существует или нет указанный индекс, без побочного эффекта создания элемента, если его не было. Это выражение имеет значение один (true) если array[index] существует, и ноль (false), если он не существует.

Например, чтобы проверить, содержит ли массив frequencies индекс `2', можно написать такой оператор:

if (2 in frequencies)
print "Subscript 2 is present."

Заметьте, что это не есть тест на то, содержит ли массив frequencies элемент со значением два. (Чтобы установить это, нет другого пути кроме просмотра всех его элементов.) Итак, это выражение не создает frequencies[2], в то время как следующая (неправильная) альтернатива его создает:

if (frequencies[2] != "")
print "Subscript 2 is present."

11.3 Присваивания элементам массива

В начало страницы

Элементы массива есть lvalues: им можно присваивать значения так же как переменным awk: array[subscript] = value. Здесь array означает имя массива. Выражение subscript есть индекс элемента массива, которому вы хотите присвоить значение. Выражение value определяет значение, которое присваивается элементу массива.

11.4 Примеры основных массивов

В начало страницы

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

-
if ($1 ? max)
max = $1 arr[$1] = $0 ""
END -
for (x = 1; x != max; x++)
print arr[x] ""

Первое правило следит за наибольшим из прочтенных номеров строк; оно также запоминает все строки в массиве arr с индексами, равными номерам строк. Второе правило исполняется после окончания ввода. Оно печатает все строки. Если это программа исполняется со следующим вводом:

5 I am the Five man
2 Who are you? The new number two!
4 . . . And four on the floor
1 Who is number one?
3 I three you.

выход будет таким:

1 Who is number one?
2 Who are you? The new number two!
3 I three you.
4 . . . And four on the floor
5 I am the Five man

Если номер строки повторяется, то последняя строка с этим номером перекроет остальные. Пропуски в номерах будут обрабатываться, если слегка усовершенствовать правило END программы:

END -
for (x = 1; x != max; x++)
if (x in arr)
print arr[x] ""

11.5 Перебор всех элементов массива

В начало страницы

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

for (var in array)
body

Этот цикл выполняет body один раз для каждого индекса в массиве, который ваша программа ранее использовала, с переменной var в качестве очередного индекса. Приведем программу, пользующуюся этой формой оператора for. Первое правило перебирает входные записи и отмечает, какие слова появились во вводе ( по крайней мере один раз), помещая их в массив со словами в качестве индексов. Второе правило просматривает все найденные элементы массива used, использованные для нахождения всех отдельных слов во вводе. Оно печатает каждое слово длиннее 10 символов и количество таких слов. См. раздел 12.3 [Встроенные функции для действий с цепочками], стр. 137, для сведений о встроенной функции length.

# Записываем 1 для каждого слова, встретившегося хотя бы один раз -
for (i = 1; i != NF; i++)
used[$i] = 1 ""
# Находим количество отдельных слов длиннее 10 символов.
END -
for (x in used)
if (length(x) ? 10) -
++num.long.words print x "" print num.long.words, "слов длиннее чем&
 10 символов" ""

См. раздел 16.2.5 [Генерирование счетчиков, использующих слова], page 235, содержащий более подробный пример такого вида.

Порядок, в котором перебираются элементы массива в таких циклах, определяется внутренней организацией массивов и не может управляться или изменяться. Если новые элементы добавлять в массив операторами в теле такого цикла, это может привести к ошибкам; нельзя быть уверенным в том, что оператор цикла найдет их. Точно так же, изменение var внутри цикла может дать непредвиденные результаты. Необходимо избегать таких действий.

11.6 Оператор delete (вычеркивание)

В начало страницы

Можно удалять отдельные элементы массива, используя оператор delete: delete array[index]. Если вы вычеркнули элемент массива, вы не можете больше узнать значение, которое этот элемент имел. Дело обстоит так, как будто на него никогда не ссылались и не присваивали никакого значения. Вот пример вычеркивания элементов массива:

for (i in frequencies)
delete frequencies[i]

В этом примере удаляются все элементы массива frequencies. Если удален какой нибудь элемент, то никакой оператор for просмотра массива не обнаружит этот элемент и выдаст ноль (т.e. false):

delete foo[4] if (4 in foo)
print "Это никогда не будет напечатано"

Важно заметить, что вычеркнуть элемент есть не то же самое, что присвоить ему пустое значение (пустую цепочку ""):

foo[4] = "" if (4 in foo)
print "Это будет напечатано, хотя foo[4] теперь пусто"

Вычеркивание несуществующего элемента не считается ошибкой. Можно вычеркнуть все элементы массива одним оператором, убрав индекс в операторе delete: delete array . Эта возможность есть расширение gawk; она не доступна в режиме совместимости (см. раздел 14.1 [Параметры командной строки], стр.161). Эта версия оператора delete почти в три раза эффективнее, чем вычеркивание по одному элементу за раз.

Следующий оператор представляет переносимый, но не очевидный способ очистки массива.

# спасибо to Michael Brennan за указание этого способа
split("", array)

Функция split (см. раздел 12.3 [Встроенные функции для действий с цепочками], стр. 137) сначала очищает указанный массив. Этот вызов просит ее разделить пустую строку. Поскольку нет данных для разделения, функция только очищает массив.

ВНИМАНИЕ: Вычеркивание массива не меняет его тип; нельзя вычеркнуть массив и затем использовать его имя как скаляр. Например, такое не сработает:

a[1] = 3; delete a; a = 3

11.7 Употребление чисел для индексации массивов

В начало страницы

Важно помнить, что индексы массивов всегда цепочки. Если в качестве индексов используются числовые значения, они превращаются в цепочки перед использованием для индексирования (см. раздел 7.4 [Конверсии цепочек и чисел], стр. 81). Это значит, что значение встроенной переменной CONVFMT может потенциально воздействовать на процесс доступа вашей программы к элементам массивов.

например:

xyz = 12.153 data[xyz] = 1 CONVFMT = "%2.2f" if (xyz in data)
printf "%s is in data"n", xyz else
printf "%s is not in data"n", xyz

Будет напечатано `12.15 is not in data'. Первый оператор дает xyz численное значение. Присваивание data[xyz] индексирует data строковым значением "12.153" (по умолчанию используется для конверсии значение CONVFMT, "%.6g"), и присваивает единицу элементу data["12.153"]. Затем программа меняет значение CONVFMT. Тест `(xyz in data)' генерирует новое строковое значение из xyz, на этот раз "12.15", поскольку значение CONVFMT допускает только две значащих цифры. Тест выдает false, поскольку "12.15" есть цепочка, отличная от "12.153".

В соответствии с правилами конверсии (см. раздел 7.4 [конверсии цепочек и чисел], стр. 81), целые числа всегда конвертируются в цепочки как целые, независимо от значения CONVFMT.

Так, оператор:

for (i = 1; i != maxsub; i++)
do something with array[i]

будет работать независимо от значения  CONVFMT.

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

11.8 Использование неинициализированных переменных в качестве индексов

В начало страницы

Предположим, что нужно напечатать входные данные в обратном порядке. Такая программа (с некоторыми тестовыми данными) может выглядеть так:

$ echo 'line 1 ? line 2 ? line 3'
-- awk '- l[lines] = $0; ++lines "" ? END
- ? for (i = lines-1; i ?= 0; --i) ? print l[i] ? ""'
a line 3 a line 2

К несчастью, самая первая строка ввода не появится на выходе! На первый взгляд программа должна работать. Переменная lines не инициализирована, а неинициализированная имеет численное значение ноль. Кажется, значение l[0] должно быть напечатано. Но индексы в массивах awk всегда строковые значения. И неинициализированные переменные, используемые как цепочки, имеют значения "", не нули. Таким образом `line 1' хранится в l[""].

Следующая версия программы работает правильно:

- l[lines++] = $0 "" END -
for (i = lines - 1; i ?= 0; --i)
print l[i] ""

Здесь `++' заставляет lines быть числом, делая "старое значение" числом ноль, которое затем конвертируется в "0" как индекс массива.

Как мы только что видели, хотя это несколько необычно, пустая цепочка ("") есть действующий индекс массива (d.c.). Если `--lint' выдано в командной строке (см. раздел 14.1 [Параметры командной строки], стр. 161), gawk будет предупреждать об использовании пустой строки в качестве индекса.

11.9 Многомерные массивы

В начало страницы

Многомерный массив --- такой массив, в котором элемент указывается последовательностью индексов, а не одним индексом. Например, двумерный массив требует два индекса. Обычный способ (в большинстве языков, включая awk) ссылаться на элемент двумерного массива с именем grid есть grid[x,y].

Многомерные массивы в awk поддерживаются конкатенацией индексов в одну цепочку. Точнее, awk конвертирует индексы в цепочки (см. раздел 7.4 [Конверсия цепочек и чисел], стр. 81) и сцепляет их вместе с разделителями между ними. Это создает одну цепочку, которая описывает значения всех индексов. Комбинированная цепочка используется как один индекс в обычном одномерном массиве. В качестве сепаратора используется значение переменной SUBSEP.

Например, предположим, что вычисляем выражение `foo[5,12] = "value"' при значении SUBSEP равном "@". Номера пять и 12 конвертируются в строки и соединяются с `@' между ними, что приводит к "5@12"; таким образом, элементу foo["5@12"] присваивается в качестве значения "value".

Когда запоминается элемент массива, для awk безразлично, был ли он с одним индексом или с последовательностью индексов. Два выражения `foo[5,12]' и `foo[5 SUBSEP 12]' всегда эквивалентны.

По умолчанию значением SUBSEP служит цепочка ""034", которая содержит непечатный символ, появление которого маловероятно в awk-программе или во входных данных. Польза выбора маловероятных символов проистекает из факта, что значения индекса, содержащего цепочку, соответствующую SUBSEP, иногда приводят к комбинированным цепочкам не взаимно однозначно. Предположим, что SUBSEP была бы "@"; тогда `foo["a@b", "c"]' и `foo["a", "b@c"]' были бы неразличимыми, так как оба запомнятся как `foo["a@b@c"]'.

Можно проверить, имеется ли определенная индексная последовательность в "многомерном" массиве с помощью того же самого оператора `in', используемого для одномерных массивов. Вместо единственного индекса в позиции левого операнда нужно писать всю последовательность индексов, разделенных запятыми и заключенную в скобки: (subscript1, subscript2, ...) in array.

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

awk '-
if (max.nf ! NF)
max.nf = NF max.nr = NR for (x = 1; x != NF; x++)
vector[x, NR] = $x ""

END -
for (x = 1; x != max.nf; x++) -
for (y = max.nr; y ?= 1; --y)
printf("%s ", vector[x, y]) printf(""n") "" ""'

Если задан ввод:

1 2 3 4 5 6 2 3 4 5 6 1 3 4 5 6 1 2 4 5 6 1 2 3

то программа выдаст:

4 3 2 1 5 4 3 2 6 5 4 3 1 6 5 4 2 1 6 5 3 2 1 6

11.10 Просмотр многомерных массивов

В начало страницы

Не имеется специального оператора for для просмотра "многомерных" массивов; их и не могло быть, потому что на самом деле многомерных массивов или элементов нет; есть только многомерный путь доступа к элементам массива. Однако, если ваша программа имеет массив, доступ к которому всегда многомерный, вы можете достичь эффекта в его сканировании, комбинируя сканирующий оператор for (см. раздел 11.5 [Сканирование всех элементов массива], стр. 127) с встроенной функцией split (см. раздел 12.3 [Встроенные функции для действий с цепочками], стр. 137). Это выглядит подобно следующему примеру:

for (combined in array) -
split(combined, separate, SUBSEP) ... ""

Здесь combined устанавливается на каждый комбинированный индекс массива и расщепляется на индивидуальные индексы, определяемые вхождениями значений SUBSEP. Расщепленные индексы становятся отдельными индексами измерений элемента массива array. Так, предположим что присваивали значение элементу массива array[1, "foo"]; тогда в массиве есть элемент с индексом "1"034foo". (Вспомним, что значение по умолчанию переменной SUBSEP есть символ с кодом 034.)

Раньше или позже, но оператор for найдет этот индекс и выполнит итерацию с combined равным "1"034foo". Тогда функция split, вызовется с параметрами: split("1"034foo", separate, ""034"). В результате этого separate[1] получает значение "1", а separate[2] получает значение "foo". Тем самым восстанавливается оригинальная последовательность индексов.

В начало страницы

<<< Оглавление Страницы: 11  12 >>>