11 5 23 0
Total files = 80
В этом примере мы запустили программу hours, передав ей текущий каталог для анализа. Она вывела таблицу, показывающую число файлов, изменявшихся в каждый час суток (0–23). Ниже показан код, осуществляющий вывод этой таблицы:
#!/bin/bash
# hours : сценарий для подсчета файлов по времени изменения
usage () {
echo "usage: $(basename $0) directory" >&2
}
# Убедиться, что аргумент является каталогом
if [[ ! -d $1 ]]; then
usage
exit 1
fi
# Инициализировать массив
for i in {0..23}; do hours[i]=0; done
# Собрать данные
for i in $(stat -c %y "$1"/* | cut -c 12-13); do
j=${i/#0}
((++hours[j]))
((++count))
done
# Вывести данные
echo -e "HourtFilestHourtFiles"
echo -e "----t-----t----t-----"
for i in {0..11}; do
j=$((i + 12))
printf "%02dt%dt%02dt%dn" $i ${hours[i]} $j ${hours[j]}
done
printf "nTotal files = %dn" $count
Сценарий состоит из одной функции (usage) и основного тела с четырьмя разделами. В первом разделе проверяется, является ли аргумент командной строки именем каталога. Если нет, сценарий выводит сообщение с информацией о порядке использования и завершается.
Второй раздел инициализирует массив hours. Для этого каждому элементу массива присваивается значение 0. Массивы не требуют специальной инициализации перед использованием, но нашему сценарию важно, чтобы в массиве не оставалось элементов с пустыми значениями. Обратите внимание на необычный способ организации цикла. Используя подстановку в фигурных скобках ({0..23}), мы смогли без труда сгенерировать последовательность слов для команды for.
Следующий раздел осуществляет сбор данных, вызывая программу stat для каждого файла в каталоге. С помощью cut из результата извлекается двузначный час. Внутри цикла выполняется удаление ведущих нулей из поля с часом, потому что иначе командная оболочка попытается (и, в конечном счете, потерпит неудачу) интерпретировать значения с 00 по 09 как восьмеричные числа (см. табл. 34.1). Далее сценарий увеличивает на единицу значение элемента массива, соответствующего полученному часу дня. Наконец, будет увеличен счетчик (count), хранящий общее число файлов в каталоге.
Последний раздел в сценарии выводит содержимое массива. Сначала выводится пара строк заголовка, а затем начинает выполняться цикл, осуществляющий вывод в четыре колонки. В заключение выводится общее число файлов.
Массивы поддерживают множество типовых операций, таких как удаление массивов, определение их размеров, сортировка и др., которым можно найти много вариантов применения в сценариях.
Вывод содержимого всего массива
Для доступа к каждому элементу массива используются индексы * и @. Так же как и в случае с позиционными параметрами, индекс @ имеет большую практическую ценность. Например:
[ [email protected] ~]$ animals=("a dog" "a cat" "a fish")
[ [email protected] ~]$ for i in ${animals[*]}; do echo $i; done
a
dog
a
cat
a
fish
[ [email protected] ~]$ for i in ${animals[@]}; do echo $i; done
a
dog
a
cat
a
fish
[ [email protected] ~]$ for i in "${animals[*]}"; do echo $i; done
a dog a cat a fish
[ [email protected] ~]$ for i in "${animals[@]}"; do echo $i; done
a dog
a cat
a fish
Мы создали массив animals и сохранили в нем три строки по два слова в каждой. Затем выполнили четыре цикла, чтобы посмотреть, как выполняется разбиение содержимого массива на слова. Инструкции ${animals[*]} и ${animals[@]} действуют идентично, если они не заключены в кавычки. Индекс * возвращает содержимое массива, разбитое на отдельные слова, тогда как индекс @ возвращает три «слова», соответствующие «истинному» содержимому массива.
Определение числа элементов в массиве
Определить число элементов в массиве, так же как длину строки, можно с помощью механизма подстановки параметров. Например:
[ [email protected] ~]$ a[100]=foo
[ [email protected] ~]$ echo ${#a[@]} # число элементов в массиве
1
[ [email protected] ~]$ echo ${#a[100]} # длина элемента с индексом 100
3
Мы создали массив a и записали строку foo в элемент с индексом 100. Далее с помощью механизма подстановки параметров мы определили длину массива, используя при этом форму записи индекса @. Затем определили длину элемента с индексом 100, содержащего строку foo. Обратите внимание, что даже при том, что мы присвоили строку элементу с индексом 100, bash сообщает, что в массиве имеется только один элемент. Такое поведение необычно для тех языков, в которых неиспользуемые элементы массива (элементы с индексами 0–99) были бы инициализированы пустыми значениями и учитывались бы при определении размера массива.
Поиск используемых индексов
Так как bash позволяет создавать разреженные массивы путем присваивания значений отдельным элементам, иногда требуется определить, какие элементы действительно существуют. Это можно сделать с помощью механизма подстановки параметров, как показано ниже:
${!массив[*]}
${!массив[@]}
где массив — это имя переменной-массива. Как и в других случаях использования * и @ в операциях подстановки, форма @, заключенная в кавычки, оказывается наиболее полезной, так как выполняет подстановку нераздробленных значений элементов:
[ [email protected] ~]$ foo=([2]=a [4]=b [6]=c)
[ [email protected] ~]$ for i in "${foo[@]}"; do echo $i; done
a
b
c
[ [email protected] ~]$ for i in "${!foo[@]}"; do echo $i; done
2
4
6
Добавление элементов в конец массива
Знание количества элементов в массиве не поможет, если понадобится добавить значения в конец массива, потому что значения, возвращаемые индексами * и @, не сообщают наибольший занятый индекс в массиве. К счастью, командная оболочка предоставляет собственное решение. Оператор присваивания += автоматически добавляет значения в конец массива. Ниже мы записали три значения в массив, а затем добавили в конец еще три.
[ [email protected] ~]$ foo=(a b c)
[ [email protected] ~]$ echo ${foo[@]}
a b c
[ [email protected] ~]$ foo+=(d e f)
[ [email protected] ~]$ echo ${foo[@]}
a b c d e f
Так же как и при работе с электронными таблицами, при работе с массивами часто возникает необходимость отсортировать значения. Командная оболочка не имеет прямой поддержки операции сортировки, но ее нетрудно реализовать самому:
#!/bin/bash
# array-sort : сортировка массива
a=(f e d c b a)
echo "Original array: ${a[@]}"
a_sorted=($(for i in "${a[@]}"; do echo $i; done | sort))
echo "Sorted array: ${a_sorted[@]}"
Если запустить этот сценарий, он выведет следующее:
[ [email protected] ~]$ array-sort
Original array: f e d c b a
Sorted array: a b c d e f
Сценарий копирует содержимое исходного массива (a) во второй массив (a_sorted), выполняя трюк с подстановкой команды. Этот простой прием можно использовать для выполнения самых разных операций с массивами, просто изменяя состав конвейера.
Удалить массив можно с помощью команды unset:
[ [email protected] ~]$ foo=(a b c d e f)
[ [email protected] ~]$ echo ${foo[@]}
a b c d e f
[ [email protected] ~]$ unset foo
[ [email protected] ~]$ echo ${foo[@]}
[ [email protected] ~]$
Командой unset можно также удалить единственный элемент массива:
[ [email protected] ~]$ foo=(a b c d e f)
[ [email protected] ~]$ echo ${foo[@]}
a b c d e f
[ [email protected] ~]$ unset 'foo[2]'
[ [email protected] ~]$ echo ${foo[@]}
a b d e f
В этом примере мы удалили третий элемент массива, с индексом 2. Не забывайте, что индексация элементов массива начинается с 0, а не с 1! Отметьте также, что элемент массива нужно заключить в кавычки, чтобы предотвратить подстановку путей оболочкой.
Интересно отметить, что присваивание пустого значения массиву не уничтожает его содержимое:
[ [email protected] ~]$ foo=(a b c d e f)
[ [email protected] ~]$ foo=
[ [email protected] ~]$ echo ${foo[@]}
b c d e f
Любая ссылка на переменную-массив без индекса возвращает элемент с индексом 0:
[ [email protected] ~]$ foo=(a b c d e f)
[ [email protected] ~]$ echo ${foo[@]}
a b c d e f
[ [email protected] ~]$ foo=A
[ [email protected] ~]$ echo ${foo[@]}
A b c d e f
Если на странице справочного руководства (man) для bash выполнить поиск слова array, можно найти множество его упоминаний, где описываются приемы работы с переменными-массивами. Большая часть этих описаний довольно туманна, но иногда они содержат весьма полезные сведения. Фактически массивы недостаточно широко используются в программировании на языке командной оболочки, в основном потому, что традиционные командные оболочки для Unix (такие, как sh) не поддерживают их. Об этом недостатке остается только сожалеть, потому что массивы очень популярны в других языках программирования и являются мощным инструментом, позволяющим решать многие задачи программирования.
Массивы и циклы по своей природе близки друг другу и часто используются вместе. Например, следующая форма цикла хорошо подходит для вычисления индексов массива:
for ((выражение1; выражение2; выражение3))
В этой главе, завершающей наше путешествие, мы обратимся к совершенно случайным темам. Несмотря на то что в предыдущих главах мы рассмотрели множество основных тем, немало особенностей bash остались неохваченными. Многие из них плохо освещены в документации и полезны в основном для тех, кто занимается интеграцией bash в дистрибутивы Linux. Но есть среди них и такие, которые, хотя и используются нечасто, могут пригодиться при решении некоторых задач программирования. Их-то мы и рассмотрим.