User Tools

Site Tools


bash:массивы

bash - Массивы

В предыдущей статье мы научились работать в командной оболочке со строками и числами. Типы данных, которые мы рассматривали до сих пор, в компьютерных кругах известны как скалярные переменные, то есть переменные, способные хранить единственное значение.

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

Что такое массивы?

Массивы – это переменные, хранящие более одного значения. Массивы организованы подобно таблице. Возьмем, к примеру, электронную таблицу. Электронная таблица действует подобно двумерному массиву. В ней есть строки и столбцы, и каждая отдельная ячейка имеет свой адрес, определяемый номером строки и номером столбца. Массив устроен аналогично. Массив состоит из ячеек, которые называют элементами, и каждый элемент содержит данные. Доступ к отдельному элементу осуществляется с использованием его адреса, который называется индексом.

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

Массивы в bash ограничены единственным измерением. Их можно рассматривать как электронные таблицы с единственным столбцом. Но даже с этим ограничением массивам можно найти массу применений. Впервые поддержка массивов появилась в bash версии 2. Оригинальная командная оболочка Unix sh вообще не поддерживала их.

Создание массива

Переменным-массивам можно давать такие же имена, что и другим переменным bash, и они точно так же создаются автоматически при первом обращении к ним. Например:

a[1]=foo
echo ${a[1]}
foo

Это пример присваивания значения элементу массива и обращения к нему. Первая команда присваивает значение foo элементу массива a с индексом 1. Вторая команда выводит значение, хранящееся в элементе с индексом 1. Использование фигурных скобок во второй команде является обязательным условием, иначе командная оболочка будет пытаться выполнить подстановку пути, опираясь на имя элемента массива.

Массив можно также создать командой declare:

declare -a a

Параметр <html>-a</html> в этом примере требует от declare создать массив (array) с именем a.

Присваивание значений массиву

Значения элементам массивов можно присваивать одним из двух способов. Присваивание одиночных значений осуществляется с использованием следующего синтаксиса:

имя[индекс]=значение

где имя – это имя массива, индекс – целое число (или арифметическое выражение) больше или равное 0. Обратите внимание, что первый элемент массива имеет индекс 0, а не 1. значение — строка или целое число, присваиваемое элементу массива.

Присвоить сразу множество значений можно с использованием следующего синтаксиса:

имя=(значение1 значение2 ...)

где имя – это имя массива, а значение1 значение2 … – значения, присваиваемые последовательным элементам массива, начиная с элемента с индексом 0. Например, если понадобится присвоить элементам массива days сокращенные названия дней недели, это можно сделать так:

days=(Sun Mon Tue Wed Thu Fri Sat)

Можно присваивать значения конкретным элементам, указывая индекс для каждого значения:

 days=([0]=Sun [1]=Mon [2]=Tue [3]=Wed [4]=Thu [5]=Fri [6]=Sat)

Доступ к элементам массива

Итак, где могут пригодиться массивы? Так же как многие задачи управления данными могут решаться с применением программ электронных таблиц, массивы могут применяться для решения множества задач программирования.

Рассмотрим простой пример сбора и представления данных. Напишем сценарий, проверяющий время последнего изменения файлов в указанном каталоге. На основе полученных данных сценарий будет выводить таблицу, показывающую, сколько файлов было изменено в каждый час суток. Такой сценарий можно использовать, например, для выяснения периодов наибольшей активности системы. Сценарий с названием <html>hours</html> производит следующий результат:

hours .
Hour   Files    Hour   Files
----   -----    ----   -----
00     0        12     11
01     1        13     7
02     0        14     1
03     0        15     7
04     1        16     6
05     1        17     5
06     6        18     4
07     3        19     4
08     1        20     1
09     14       21     0
10     2        22     0
11     5        23     0
 
Total files = 80

В этом примере мы запустили программу <html>hours</html>, передав ей текущий каталог для анализа. Она вывела таблицу, показывающую число файлов, изменявшихся в каждый час суток (0–23). Ниже показан код, осуществляющий вывод этой таблицы:

#!/bin/bash
 
# hours : сценарий для подсчета файлов по времени изменения
 
usage () {
    echo "usage: ${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 "Hour\tFiles\tHour\tFiles"
echo -e "----\t-----\t----\t-----"
for i in {0..11}; do
    j=$((i + 12))
    printf "%02d\t%d\t%02d\t%d\n" \
	"$i" \
	"${hours[i]}" \
	"$j" \
	"${hours[j]}"
done
printf "\nTotal files = %d\n" $count

Сценарий состоит из одной функции (usage) и основного тела с четырьмя разделами. В первом разделе проверяется, является ли аргумент командной строки именем каталога. Если нет, сценарий выводит сообщение с информацией о порядке использования и завершается.

Второй раздел инициализирует массив <html>hours</html>. Для этого каждому элементу массива присваивается значение 0. Массивы не требуют специальной инициализации перед использованием, но нашему сценарию важно, чтобы в массиве не оставалось элементов с пустыми значениями. Обратите внимание на необычный способ организации цикла. Используя подстановку в фигурных скобках ({0..23}), мы смогли без труда сгенерировать последовательность слов для команды for.

Следующий раздел осуществляет сбор данных, вызывая программу stat для каждого файла в каталоге. С помощью cut из результата извлекается двузначный час. Внутри цикла выполняется удаление ведущих нулей из поля с часом, потому что иначе командная оболочка попытается (и, в конечном счете, потерпит неудачу) интерпретировать значения с 00 по 09 как восьмеричные числа. Далее сценарий увеличивает на единицу значение элемента массива, соответствующего полученному часу дня. Наконец, будет увеличен счетчик (count), хранящий общее число файлов в каталоге.

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

Операции с массивами

Массивы поддерживают множество типовых операций, таких как удаление массивов, определение их размеров, сортировка и др., которым можно найти много вариантов применения в сценариях.

Вывод содержимого всего массива

Для доступа к каждому элементу массива используются индексы <html>*</html> и <html>@</html>. Так же как и в случае с позиционными параметрами, индекс <html>@</html> имеет большую практическую ценность. Например:

animals=("a dog" "a fish" "a cat")
 
for i in ${animals[*]}; do echo $i; done
a
dog
a
fish
a
cat
 
for i in ${animals[@]}; do echo $i; done
a
dog
a
fish
a
cat
for i in "${animals[*]}"; do echo $i; done
a dog a fish a cat
 
for i in "${animals[@]}"; do echo $i; done
a dog
a fish
a cat

Выше был создан массив animals и в нем сохранено три строки по два слова в каждой. Затем выполнили четыре цикла, чтобы посмотреть, как выполняется разбиение содержимого массива на слова. Инструкции <html>${animals[*]}</html> и <html>${animals[@]}</html> действуют идентично, если они не заключены в кавычки. Индекс <html>*</html> возвращает содержимое массива, разбитое на отдельные слова, тогда как индекс <html>@</html> возвращает три «слова», соответствующие «истинному» содержимому массива.

Определение числа элементов в массиве

Определить число элементов в массиве, так же как длину строки, можно с помощью механизма подстановки параметров. Например:

a[100]=foo
echo ${#a[@]} # число элементов в массиве
1
 
echo ${#a[100]}  # длина элемента с индексом 100
3

Мы создали массив a и записали строку foo в элемент с индексом 100. Далее с помощью механизма подстановки параметров мы определили длину массива, используя при этом форму записи индекса <html>@</html>. Затем определили длину элемента с индексом 100, содержащего строку foo. Обратите внимание, что даже притом, что мы присвоили строку элементу с индексом 100, bash сообщает, что в массиве имеется только один элемент. Такое поведение необычно для тех языков, в которых неиспользуемые элементы массива (элементы с индексами 0–99) были бы инициализированы пустыми значениями и учитывались бы при определении размера массива. В bash элемент массива существует, только если ему присвоено значение, независимо от индекса.

Поиск используемых индексов

Так как bash позволяет создавать разреженные массивы путем присваивания значений отдельным элементам, иногда требуется определить, какие элементы действительно существуют. Это можно сделать с помощью механизма подстановки параметров, как показано ниже:

${!массив[*]}
${!массив[@]}

где массив – это имя переменной-массива. Как и в других случаях использования <html>*</html> и <html>@</html> в операциях подстановки, форма <html>@</html>, заключенная в кавычки, оказывается наиболее полезной, так как выполняет подстановку не раздробленных значений элементов:

foo=([2]=a [10]=b [11]=c)
 
for i in "${foo[@]}"; do echo $i; done
a
b
c
 
for i in "${!foo[@]}"; do echo $i; done
2
10
11

Добавление элементов в конец массива

Знание количества элементов в массиве не поможет, если понадобится добавить значения в конец массива, потому что значения, возвращаемые индексами <html>*</html> и <html>@</html>, не сообщают наибольший занятый индекс в массиве. К счастью, командная оболочка предоставляет собственное решение. Оператор присваивания <html>+=</html> автоматически добавляет значения в конец массива. Ниже мы записали три значения в массив, а затем добавили в конец еще три.

foo=(a b c)
echo ${foo[@]}
a b c
 
foo+=(d e f)
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[@]}"

Если запустить этот сценарий, он выведет следующее:

Original array: f e d c b a
Sorted array: a b c d e f

Сценарий копирует содержимое исходного массива (a) во второй массив (a_sorted), выполняя трюк с подстановкой команды. Этот простой прием можно использовать для выполнения самых разных операций с массивами, просто изменяя состав конвейера.

Удаление массива

Удалить массив можно с помощью команды unset:

foo=(a b c d e f)
echo ${foo[@]}
a b c d e f
 
unset foo
echo ${foo[@]}

Командой unset можно также удалить единственный элемент массива:

foo=(a b c d e f)
echo ${foo[@]}
a b c d e f
 
unset 'foo[2]'
echo ${foo[@]}
a b d e f

В этом примере мы удалили третий элемент массива, с индексом 2. Не забывайте, что индексация элементов массива начинается с 0, а не с 1! Отметьте также, что элемент массива нужно заключить в кавычки, чтобы предотвратить подстановку путей оболочкой.

Интересно отметить, что присваивание пустого значения массиву не уничтожает его содержимое:

foo=(a b c d e f)
foo=
echo ${foo[@]}
b c d e f

Любая ссылка на переменную-массив без индекса возвращает элемент с индексом 0:

foo=(a b c d e f)
echo ${foo[@]}
a b c d e f
 
foo=A
echo ${foo[@]}
A b c d e f

Ассоциативные массивы

bash версии 4.0 и выше поддерживает ассоциативные массивы. Для индексации элементов в ассоциативных массивах используются строки, а не целые числа, как в обычных массивах. Эта возможность открывает новые подходы к управлению данными. Например, можно создать массив colors и в качестве индексов использовать названия цветов:

declare -A colors
colors["red"]="#ff0000"
colors["green"]="#00ff00"
colors["blue"]="#0000ff"

В отличие от обычных массивов с целочисленной индексацией, которые создаются простой ссылкой на них, ассоциативные массивы можно создать только командой declare с новым параметром <html>-A</html>. Доступ к элементам ассоциативного массива осуществляется во многом так же, как к элементам обычных массивов с целочисленными индексами

echo ${colors[blue]}
#0000ff
bash/массивы.txt · Last modified: 2023/04/06 10:18 (external edit)