В bash можно создавать многомерные структуры, но выглядеть они будут ужасно и такой усложненный код сложнее поддерживать; ⇒ если нужны такие сложные структуры, то лучше реализовать их на другом ЯП.
Не все версии bash поддерживают хеши
Поддержка хешей появилась только в bash 4.0, после чего потребовалось еще пару релизов, чтобы отшливофать некоторые детали. К примеру, только в 4.3 в стало возможным юзать $list[-1] для ссылки на последний элемент вместо вот этого трэша:
$mylist[${#mylist[*]}-1]# где ${#mylist[*] - кол-во элементов
Массивы не POSIX
Массивы (как списки, так и хеши) не стандартизированы в POSIX. ⇒ если парит переносимость кода за пределы bash - будь осторожен (ибо, например, синтаксис zsh отличается).
Случайное присваивание
Присваивание без указания нижнего индекса изменит нулевой элемент. ⇒myarray=fooсоздаст/изменит$myarray[0] (даже если это хеш!).
Про [@] и [*]
Индекс [@] или [*] возвращает все элементы.
Они различаются только когда ссылка на массив заключается в двойные кавычки " ". $name[*] развернется в одну строку $name[@] развернется в коллекцию строк с отдельными элементами массива.
Так что юзать: [@] или [*]?
Почти во всех случаях следует юзать [@].
Массивы (списки)
Объявление
Массивы (списки в bash так называются) могут объявляться w/:
declare -a mylistlocal -a mylistreadonly -a mylist
или простым присваниванием:
mylist[0]=foomylist=() # пустой список
Добавление элемента
После объявления списка можно добавлять элементы с помощью такого присваивания:
declare -a mylistmylist+=(foo bar buz)# илиmylist+=("abo bus")# илиmylist+=(foo bar "abo bus" buz)
Длина списка или элемента
echo "The element count is: ${#mylist[@]} or ${#mylist[*]}"# The element count is: 4 or 4echo "The length of element [3] is ${#mylist[3]}"# The length of element [3] is 7
Объединение элементов
function Join { local IFS="$1"; shift; echo "$*"; } # односимвольный разделительecho -n "Join ',' \${mylist[@] = "; Join ',' "${mylist[@]}"# Join ',' ${mylist[@] = foo,bar,buz,abo bus
Обход значений
for element in "${mylist[@]}"; do echo "$element"done
Обход значений с индексами элементов
Для этого нужен восклицательный знак ! перед названием списка.
for element in "${!mylist[@]}"; do echo -e "\tElement: $element; value: ${mylist[$element]}"done# Element: 0; value: foo# Element: 1; value: bar# Element: 2; value: abo bus# Element: 3; value: buz
Операции со срезами (slices)
Вывод
printf "%q|" "${mylist[@]:3:1}"# buz|
Присваивание среза
# Срез, начинающийся с первого элемент, кавычки обязательныmylist=("${mylist[@]:1}")# Срез, начинающийся с элемента #countmylist=("$mylist[@]:$count")
Удаление (pop) последнего элемента
unset -v 'mylist[-1]' # В bash 4.3+# unset -v 'mylist[${#mylist[*]}-1]' # В старых версиях
Удаление срезов
unset -v 'mylist[2]' # Delete element 2
Удаление всего списка
unset -v 'mylist'
Осторожнее с unset
Если в файловой системе будет файл с именем, совпадающим с именем переменной, то поддержка подстановки имен файлов в командной оболочке может удалить этот файл.
Чтобы избежать этого, нужно:
заключить переменную в кавычки
юзать ключ -v, чтобы unset рассматривал аргумент как переменную.
Хеши (словари)
Объявление
Хеши (словари) обязательно должны объявляться w/:
declare -A myhashlocal -A myhashreadonly -A myhash
Присваивание
declare -A myhashmyhash[a]='foo'myhash[b]='bar'myhash[c]='aasasa ada asasas'# илиmyhash=([a]=foo [b]=bar [c]="aasasa ada asasas")
Вывод некоторых деталей и контента
echo "The key count is: ${#myhash[@]} or ${#myhash[*]}"# The key count is: 3 or 3echo "The length of the value of key [b] is: ${#myhash[b]}"# The length of the value of key [b] is: 3declare -p myhashecho -n "\${myhash[@]} = " ; printf "%q|" ${myhash[@]}# ${myhash[@]} = aasasa|ada|asasas|bar|foo|
Объединение значений
function Join { local IFS="$1"; shift; echo "$*"; } # односимвольный разделительecho -n "Join ',' \${myhash[@] = "; Join ',' "${myhash[@]}"# Join ',' ${myhash[@] = aasasa ada asasas,bar,foo
Операции со срезами
Но работа со срезами выглядит странно, ибо индексы не порядковые номера.
Самое распространенное применение хешей - подсчет слов и/или “уникализация” элементов.
#!/bin/bash -WORD_FILE='/tmp/words.txt'> "$WORD_FILE" # Создание файлаtrap "rm -f $WORD_FILE" ABRT EXIT HUP INT QUIT TERMdeclare -A MY_HASH# Создание списка словMY_LIST=(foo bar baz one two three four)# Выбор случайных элементов из списка в циклеrange="${#MY_LIST[@]}"for ((i=0; i<35; i++)); do random_element="$(( $RANDOM % $range ))" echo "${MY_LIST[$random_element]}" >> $WORD_FILEdone# Запись слов из списка в хешwhile read line; do # увеличение значения счетчика для уже встречающегося слова (( MY_HASH[$line]++ ))done < "$WORD_FILE"# Обход ключей для вывода списка слов# без повторений и без использования внешней команды uniqecho -e "\nUnique words from: $WORD_FILE"for key in "${!MY_HASH[@]}"; do echo "$key"done | sort# Повторный обход ключей для отображения значений счетчиков словecho -e "\nWord counts, ordered by word, from: $WORD_FILE"for key in "${!MY_HASH[@]}"; do printf "%s\t%d\n" $key ${MY_HASH[$key]}done | sort# Последний обход ключей для отображения счетчиков,# но на этот раз с числовой сортировкой по второму полю (sort -k2,2n)echo -e "\nWord counts, ordered by count, from: $WORD_FILE"for key in "${!MY_HASH[@]}"; do printf "%s\t%d\n" $key ${MY_HASH[$key]}done | sort -k2,2n
stdout:
Unique words from: /tmp/words.txt
bar
baz
foo
four
one
three
two
Word counts, ordered by word, from: /tmp/words.txt
bar 7
baz 4
foo 5
four 6
one 2
three 4
two 7
Word counts, ordered by count, from: /tmp/words.txt
one 2
baz 4
three 4
foo 5
four 6
bar 7
two 7
Другие сценарии использования
Перенаправление результата выполнения команды в массив
mylist=(foo bar baz one two three four)range"${#mylist[@]}"for ((i=0; i<35; i++)); do random_element="$(( $RANDOM % $range ))" echo "${mylist[$random_element]}"done
Запись из списка/файла в хеш
while read line; do # увеличение значения счетчика для уже встречающегося слова (( myhash[$line]++ ))done < $WORD_FILE # или $mylist