Массивы
Advanced Bash-Scripting Guide: Искусство программирования на языке сценариев командной оболочки; Версия 2.5 (15 февраля 2004) | ||
---|---|---|
Назад | Вперед |
Глава 25. Массивы
Новейшие версии Bash поддерживают одномерные массивы. Инициализация элементов массива может быть произведена в виде: variable[xx]. Можно явно объявить массив в сценарии, с помощью директивы declare: declare -a variable. Обращаться к отдельным элементам массива можно с помощью фигурных скобок, т.е.: ${variable[xx]}.
Пример 25-1. Простой массив
#!/bin/bash area[11]=23 area[13]=37 area[51]=UFOs # Массивы не требуют, чтобы последовательность элементов в массиве была непрерывной. # Некоторые элементы массива могут оставаться неинициализированными. # "Дыркм" в массиве не являются ошибкой. echo -n "area[11] = " echo ${area[11]} # необходимы {фигурные скобки} echo -n "area[13] = " echo ${area[13]} echo "содержимое area[51] = ${area[51]}." # Обращение к неинициализированным элементам дает пустую строку. echo -n "area[43] = " echo ${area[43]} echo "(элемент area[43] -- неинициализирован)" echo # Сумма двух элементов массива, записанная в третий элемент area[5]=`expr ${area[11]} + ${area[13]}` echo "area[5] = area[11] + area[13]" echo -n "area[5] = " echo ${area[5]} area[6]=`expr ${area[11]} + ${area[51]}` echo "area[6] = area[11] + area[51]" echo -n "area[6] = " echo ${area[6]} # Эта попытка закончится неудачей, поскольку сложение целого числа со строкой не допускается. echo; echo; echo # ----------------------------------------------------------------- # Другой массив, "area2". # И другой способ инициализации массива... # array_name=( XXX YYY ZZZ ... ) area2=( ноль один два три четыре ) echo -n "area2[0] = " echo ${area2[0]} # Ага, индексация начинается с нуля (первый элемент массива имеет индекс [0], а не [1]). echo -n "area2[1] = " echo ${area2[1]} # [1] -- второй элемент массива. # ----------------------------------------------------------------- echo; echo; echo # ----------------------------------------------- # Еще один массив, "area3". # И еще один способ инициализации... # array_name=([xx]=XXX [yy]=YYY ...) area3=([17]=семнадцать [21]=двадцать_один) echo -n "area3[17] = " echo ${area3[17]} echo -n "area3[21] = " echo ${area3[21]} # ----------------------------------------------- exit 0
Bash позволяет оперировать переменными, как массивами, даже если они не были явно объявлены таковыми. string=abcABC123ABCabc echo ${string[@]} # abcABC123ABCabc echo ${string[*]} # abcABC123ABCabc echo ${string[0]} # abcABC123ABCabc echo ${string[1]} # Ничего не выводится! # Почему? echo ${#string[@]} # 1 # Количество элементов в массиве. # Спасибо Michael Zick за этот пример.Эти примеры еще раз подтверждают отсутствие контроля типов в Bash. |
Пример 25-2. Форматирование стихотворения
#!/bin/bash # poem.sh # Строки из стихотворения (одна строфа). Line[1]="Мой дядя самых честных правил," Line[2]="Когда не в шутку занемог;" Line[3]="Он уважать себя заставил," Line[4]="И лучше выдумать не мог." Line[5]="Его пример другим наука..." # Атрибуты. Attrib[1]=" А.С. Пушкин" Attrib[2]="\"Евгений Онегин\"" for index in 1 2 3 4 5 # Пять строк. do printf " %s\n" "${Line[index]}" done for index in 1 2 # Две строки дополнительных атрибутов. do printf " %s\n" "${Attrib[index]}" done exit 0
При работе с отдельными элементами массива можно использовать специфический синтаксис, даже стандартные команды и операторы Bash адаптированы для работы с массивами.
Пример 25-3. Различные операции над массивами
#!/bin/bash # array-ops.sh: Операции над массивами. array=( ноль один два три четыре пять ) echo ${array[0]} # ноль echo ${array:0} # ноль # Подстановка параметра - первый элемент, #+ начиная с позиции 0 (с 1-го символа). echo ${array:1} # оль # Подстановка параметра - первый элемент, #+ начиная с позиции 1 (со 2-го символа). echo "--------------" echo ${#array[0]} # 4 # Длина первого элемента массива. echo ${#array} # 4 # Длина первого элемента массива. # (Альтернативный вариант) echo ${#array[1]} # 4 # Длина второго элемента массива. # Индексация массивов в Bash начинается с нуля. echo ${#array[*]} # 6 # Число элементов в массиве. echo ${#array[@]} # 6 # Число элементов в массиве. echo "--------------" array2=( [0]="первый элемент" [1]="второй элемент" [3]="четвертый элемент" ) echo ${array2[0]} # первый элемент echo ${array2[1]} # второй элемент echo ${array2[2]} # # Не был проинициализирован, поэтому null. echo ${array2[3]} # четвертый элемент exit 0
Большинство стандартных операций над строками применимы к массивам.
Пример 25-4. Строковые операции и массивы
#!/bin/bash # array-strops.sh: Строковые операции и массивы. # Автор: Michael Zick. # Используется с его разрешения. # Как правило, строковые операции, в нотации ${name ... } #+ могут использоваться для работы со строковыми элементами массивов #+ в виде: ${name[@] ... } или ${name[*] ...} . arrayZ=( one two three four five five ) echo # Извлечение части строки echo ${arrayZ[@]:0} # one two three four five five # Все элементы массива. echo ${arrayZ[@]:1} # two three four five five # Все эелементы массива, начиная со 2-го. echo ${arrayZ[@]:1:2} # two three # Два элемента массива, начиная со 2-го. echo "-----------------------" # Удаление части строки # Удаляет наименьшую из подстрок, найденых по шаблону (поиск ведется с начала строки) #+ где шаблон -- это регулярное выражение. echo ${arrayZ[@]#f*r} # one two three five five # Находит подстроку "four" и удаляет ее. # Поиск ведется по всем элементам массива # Удаляет наибольшую подстроку, из найденых по шаблону echo ${arrayZ[@]##t*e} # one two four five five # Находит подстроку "three" и удаляет ее. # Поиск ведется по всем элементам массива # Удаляет наименьшую из подстрок, найденых по шаблону (поиск ведется с конца строки) echo ${arrayZ[@]%h*e} # one two t four five five # Находит подстроку "hree" и удаляет ее. # Поиск ведется по всем элементам массива # Удаляет наибольшую из подстрок, найденых по шаблону (поиск ведется с конца строки) echo ${arrayZ[@]%%t*e} # one two four five five # Находит подстроку "three" и удаляет ее. # Поиск ведется по всем элементам массива echo "-----------------------" # Замена части строки # Заменяет первую найденую подстроку заданой строкой echo ${arrayZ[@]/fiv/XYZ} # one two three four XYZe XYZe # Поиск ведется по всем элементам массива # Заменяет все найденные подстроки echo ${arrayZ[@]//iv/YY} # one two three four fYYe fYYe # Поиск ведется по всем элементам массива # Удаляет все найденные подстроки # Если замещающая строка не задана, то это означает "удаление" echo ${arrayZ[@]//fi/} # one two three four ve ve # Поиск ведется по всем элементам массива # Заменяет первую найденную подстроку (поиск ведется с начала строки) echo ${arrayZ[@]/#fi/XY} # one two three four XYve XYve # Поиск ведется по всем элементам массива # Заменяет первую найденную подстроку (поиск ведется с конца строки) echo ${arrayZ[@]/%ve/ZZ} # one two three four fiZZ fiZZ # Поиск ведется по всем элементам массива echo ${arrayZ[@]/%o/XX} # one twXX three four five five # Почему? echo "-----------------------" # Before reaching for awk (or anything else) # Вспомним: # $( ... ) -- вызов функции. # Функция запускается как подпроцесс. # Функции выводят на stdout. # Присваивание производится со stdout функции. # Запись name[@] -- означает операцию "for-each". newstr() { echo -n "!!!" } echo ${arrayZ[@]/%e/$(newstr)} # on!!! two thre!!! four fiv!!! fiv!!! # Q.E.D: Замена -- суть есть "присваивание". # Доступ "For-Each" -- ко всем элементам массива echo ${arrayZ[@]//*/$(newstr optional_arguments)} # Now, if Bash would just pass the matched string as $0 #+ to the function being called . . . echo exit 0
Command substitution can construct the individual elements of an array.
Пример 25-5. Загрузка исходного текста сценария в массив
#!/bin/bash # script-array.sh: Сценарий загружает себя в массив. # Идею подал Chris Martin (спасибо!). script_contents=( $(cat "$0") ) # Записать содержимое этого сценария ($0) #+ в массив. for element in $(seq 0 $((${#script_contents[@]} - 1))) do # ${#script_contents[@]} #+ дает число элементов массива. # # Вопрос: # Для чего необходима команда seq 0 ? # Попробуйте заменить ее на seq 1. echo -n "${script_contents[$element]}" # Вывести элементы массива в одну строку, echo -n " -- " # разделяя их комбинацией " -- " . done echo exit 0 # Упражнение: # -------- # Попробуйте изменить сценарий таким образом, #+ чтобы он выводил себя на экран в первоначальном виде, #+ со всеми пробелами, переводами строк и т.п.
При работе с массивами, некоторые встроенные команды Bash имеют несколько иной смысл. Например, unset -- удаляет отдельные элементы массива, или даже массив целиком.
Пример 25-6. Некоторые специфичные особенности массивов
#!/bin/bash declare -a colors # Допускается объявление массива без указания его размера. echo "Введите ваши любимые цвета (разделяя их пробелами)." read -a colors # Введите хотя бы 3 цвета для демонстрации некоторых свойств массивов. # Специфический ключ команды 'read', #+ позволяющий вводить несколько элементов массива. echo element_count=${#colors[@]} # Получение количества элементов в массиве. # element_count=${#colors[*]} -- дает тот же результат. # # Переменная "@" позволяет "разбивать" строку в кавычках на отдельные слова #+ (выделяются слова, разделенные пробелами). index=0 while [ "$index" -lt "$element_count" ] do # Список всех элементов в массиве. echo ${colors[$index]} let "index = $index + 1" done # Каждый элемент массива выводится в отдельной строке. # Если этого не требуется, то используйте echo -n "${colors[$index]} " # # Эквивалентный цикл "for": # for i in "${colors[@]}" # do # echo "$i" # done # (Спасибо S.C.) echo # Еще один, более элегантный, способ вывода списка всех элементов массива. echo ${colors[@]} # ${colors[*]} дает тот же результат. echo # Команда "unset" удаляет элементы из массива, или даже массив целиком. unset colors[1] # Удаление 2-го элемента массива. # Тот же эффект дает команда colors[1]= echo ${colors[@]} # Список всех элементов массива -- 2-й элемент отсутствует. unset colors # Удаление всего массива. # Тот же эффект имеют команды unset colors[*] #+ и unset colors[@]. echo; echo -n "Массив цветов опустошен." echo ${colors[@]} # Список элементов массива пуст. exit 0
Как видно из предыдущего примера, обращение к ${array_name[@]} или ${array_name[*]} относится ко всем элементам массива. Чтобы получить количество элементов массива, можно обратиться к ${#array_name[@]} или к ${#array_name[*]}. ${#array_name} -- это длина (количество символов) первого элемента массива, т.е. ${array_name[0]}.
Пример 25-7. Пустые массивы и пустые элементы
#!/bin/bash # empty-array.sh # Выражаю свою благодарность Stephane Chazelas за этот пример, #+ и Michael Zick за его доработку. # Пустой массив -- это не то же самое, что массив с пустыми элементами. array0=( первый второй третий ) array1=( '' ) # "array1" имеет один пустой элемент. array2=( ) # Массив "array2" не имеет ни одного элемента, т.е. пуст. echo ListArray() { echo echo "Элементы массива array0: ${array0[@]}" echo "Элементы массива array1: ${array1[@]}" echo "Элементы массива array2: ${array2[@]}" echo echo "Длина первого элемента массива array0 = ${#array0}" echo "Длина первого элемента массива array1 = ${#array1}" echo "Длина первого элемента массива array2 = ${#array2}" echo echo "Число элементов в массиве array0 = ${#array0[*]}" # 3 echo "Число элементов в массиве array1 = ${#array1[*]}" # 1 (сюрприз!) echo "Число элементов в массиве array2 = ${#array2[*]}" # 0 } # =================================================================== ListArray # Попробуем добавить новые элементы в массивы # Добавление новых элементов в массивы. array0=( "${array0[@]}" "новый1" ) array1=( "${array1[@]}" "новый1" ) array2=( "${array2[@]}" "новый1" ) ListArray # или array0[${#array0[*]}]="новый2" array1[${#array1[*]}]="новый2" array2[${#array2[*]}]="новый2" ListArray # Теперь представим каждый массив как 'стек' ('stack') # Команды выше, можно считать командами 'push' -- добавление нового значения на вершину стека # 'Глубина' стека: height=${#array2[@]} echo echo "Глубина стека array2 = $height" # Команда 'pop' -- выталкивание элемента стека, находящегося на вершине: unset array2[${#array2[@]}-1] # Индексация массивов начинается с нуля height=${#array2[@]} echo echo "POP" echo "Глубина стека array2, после выталкивания = $height" ListArray # Вывести только 2-й и 3-й элементы массива array0 from=1 # Индексация массивов начинается с нуля to=2 # declare -a array3=( ${array0[@]:1:2} ) echo echo "Элементы массива array3: ${array3[@]}" # Замена элементов по шаблону declare -a array4=( ${array0[@]/второй/2-й} ) echo echo "Элементы массива array4: ${array4[@]}" # Замена строк по шаблону declare -a array5=( ${array0[@]//новый?/старый} ) echo echo "Элементы массива array5: ${array5[@]}" # Надо лишь привыкнуть к такой записи... declare -a array6=( ${array0[@]#*новый} ) echo # Это может вас несколько удивить echo "Элементы массива array6: ${array6[@]}" declare -a array7=( ${array0[@]#новый1} ) echo # Теперь это вас уже не должно удивлять echo "Элементы массива array7: ${array7[@]}" # Выглядить очень похоже на предыдущий вариант... declare -a array8=( ${array0[@]/новый1/} ) echo echo "Элементы массива array8: ${array8[@]}" # Итак, что вы можете сказать обо всем этом? # Строковые операции выполняются последовательно, над каждым элементом #+ в массиве var[@]. # Таким образом, BASH поддерживает векторные операции # Если в результате операции получается пустая строка, то #+ элемент массива "исчезает". # Вопрос: это относится к строкам в "строгих" или "мягких" кавычках? zap='новый*' declare -a array9=( ${array0[@]/$zap/} ) echo echo "Элементы массива array9: ${array9[@]}" # "...А с платформы говорят: "Это город Ленинград!"..." declare -a array10=( ${array0[@]#$zap} ) echo echo "Элементы массива array10: ${array10[@]}" # Сравните массивы array7 и array10 # Сравните массивы array8 и array9 # Ответ: в "мягких" кавычках. exit 0
Разница между ${array_name[@]} и ${array_name[*]} такая же, как между $@ и $*. Эти свойства массивов широко применяются на практике.
# Копирование массивов. array2=( "${array1[@]}" ) # или array2="${array1[@]}" # Добавить элемент. array=( "${array[@]}" "новый элемент" ) # или array[${#array[*]}]="новый элемент" # Спасибо S.C.
Операция подстановки команд -- array=( element1 element2 ... elementN ), позволяет загружать содержимое текстовых файлов в массивы. #!/bin/bash filename=sample_file # cat sample_file # # 1 a b c # 2 d e fg declare -a array1 array1=( `cat "$filename"`) # Загрузка содержимого файла # $filename в массив array1. # Вывод на stdout. # # array1=( `cat "$filename" | tr '\n' ' '`) # с заменой символов перевода строки на пробелы. # Впрочем, в этом нет необходимости, поскольку Bash #+ выполняет разбивку по словам заменяя символы перевода строки #+ на пробелы автоматически. echo ${array1[@]} # список элементов массива. # 1 a b c 2 d e fg # # Каждое "слово", в текстовом файле, отделяемое от других пробелами #+ заносится в отдельный элемент массива. element_count=${#array1[*]} echo $element_count # 8 |
Пример 25-8. Инициализация массивов
#! /bin/bash # array-assign.bash # Поскольку здесь рассматриваются операции, специфичные для Bash, #+ файл сценария имеет расширение ".bash". # Copyright (c) Michael S. Zick, 2003, All rights reserved. # Лицензионное соглашение: Допускается использование сценария # в любом виде без каких либо ограничений. # Версия: $ID$ # Основан на примере, предоставленом Stephane Chazelas, #+ который включен в состав книги: Advanced Bash Scripting Guide. # Формат вывода команды 'times': # User CPU <space> System CPU # User CPU of dead children <space> System CPU of dead children # Bash предоставляет два способа записи всех элементов #+ одного массива в другой. # В Bash, версий 2.04, 2.05a и 2.05b, #+ оба они пропускают 'пустые' элементы # В более новых версиях добавлена возможность присваивания #+ в виде [индекс]=значение. declare -a bigOne=( /dev/* ) echo echo 'Условия проверки: Отсутствие кавычек, IFS по-умолчанию, Все-Элементы' echo "Количество элементов в массиве: ${#bigOne[@]}" # set -vx echo echo '- - проверяется: =( ${array[@]} ) - -' times declare -a bigTwo=( ${bigOne[@]} ) # ^ ^ times echo echo '- - проверяется: =${array[@]} - -' times declare -a bigThree=${bigOne[@]} # Обратите внимание: круглые скобки отсутствуют. times # Сравнение временных показателей свидетельствует о том, что вторая форма записи, #+ как заметил Stephane Chazelas, работает в 3-4 раза быстрее. # Тем не менее, в своих примерах, я буду продолжать использовать первую форму записи #+ потому что, на мой взгляд, она более показательна. # Однако, в отдельных случаях, я буду использовать вторую форму записи, #+ с целью увеличения скорости исполнения сценариев. # MSZ: Прошу прощения, что не предупредил об этом заранее! exit 0
Явное объявление массива с помощью конструкции declare -a может повысить скорость работы с этим массивом в последующих операциях. |
Пример 25-9. Копирование и конкатенация массивов
#! /bin/bash # CopyArray.sh # # Автор: Michael Zick. # Используется с его разрешения. # "Принять из массива с заданным именем записать в массив с заданным именем" #+ или "собственный Оператор Присваивания". CpArray_Mac() { # Оператор Присваивания echo -n 'eval ' echo -n "$2" # Имя массива-результата echo -n '=( ${' echo -n "$1" # Имя исходного массива echo -n '[@]} )' # Все это могло бы быть объединено в одну команду. # Это лишь вопрос стиля. } declare -f CopyArray # "Указатель" на функцию CopyArray=CpArray_Mac # Оператор Присваивания Hype() { # Исходный массив с именем в $1. # (Слить с массивом, содержащим "-- Настоящий Рок-н-Ролл".) # Вернуть результат в массиве с именем $2. local -a TMP local -a hype=( -- Настоящий Рок-н-Ролл ) $($CopyArray $1 TMP) TMP=( ${TMP[@]} ${hype[@]} ) $($CopyArray TMP $2) } declare -a before=( Advanced Bash Scripting ) declare -a after echo "Массив before = ${before[@]}" Hype before after echo "Массив after = ${after[@]}" # Еще? echo "Что такое ${after[@]:4:2}?" declare -a modest=( ${after[@]:2:1} ${after[@]:3:3} ) # ---- выделение подстроки ---- echo "Массив Modest = ${modest[@]}" # А что в массиве 'before' ? echo "Массив Before = ${before[@]}" exit 0
Пример 25-10. Еще пример на конкатенацию массивов
#! /bin/bash # array-append.bash # Copyright (c) Michael S. Zick, 2003, All rights reserved. # Лицензионное соглашение: Допускается использование сценария # в любом виде без каких либо ограничений. # Версия: $ID$ # # С небольшими изменениями, внесенными автором книги. # Действия над массивами являются специфичными для Bash. # Эквиваленты в /bin/sh отсутствуют! # Чтобы избежать скроллинга выводимой информации за пределы терминала, #+ передайте вывод от сценария, через конвейер, команде 'more'. # Упакованный массив. declare -a array1=( zero1 one1 two1 ) # Разреженный массив (элемент [1] -- не определен). declare -a array2=( [0]=zero2 [2]=two2 [3]=three2 ) echo echo '- Проверка того, что массив получился разреженным. -' echo "Число элементов: 4" # Жестко "зашито", в демонстрационных целях. for (( i = 0 ; i < 4 ; i++ )) do echo "Элемент [$i]: ${array2[$i]}" done # См. так же пример basics-reviewed.bash. declare -a dest # Конкатенация двух массивов. echo echo 'Условия: Отсутствие кавычек, IFS по-умолчанию, Все-Элементы' echo '- Неопределенные элементы не передаются. -' # # На самом деле неопределенные элемены отсутствуют в массиве. dest=( ${array1[@]} ${array2[@]} ) # dest=${array1[@]}${array2[@]} # Странный результат, возможно ошибка. # Теперь выведем результат. echo echo '- - Проверка конкатенации массивов - -' cnt=${#dest[@]} echo "Число элементов: $cnt" for (( i = 0 ; i < cnt ; i++ )) do echo "Элемент [$i]: ${dest[$i]}" done # Записать массив в элемент другого массива (дважды). dest[0]=${array1[@]} dest[1]=${array2[@]} # Вывести результат. echo echo '- - Проверка записи содержимого одного массива в элемент другого массива - -' cnt=${#dest[@]} echo "Число элементов: $cnt" for (( i = 0 ; i < cnt ; i++ )) do echo "Элемент [$i]: ${dest[$i]}" done # Рассмотрение содержимого второго элемента. echo echo '- - Запись содержимого второго элемента и вывод результата - -' declare -a subArray=${dest[1]} cnt=${#subArray[@]} echo "Число элементов: $cnt" for (( i = 0 ; i < cnt ; i++ )) do echo "Элемент [$i]: ${subArray[$i]}" done # Запись содержимого целого массива в элемент другого массива, #+ с помощью конструкции '=${ ... }', #+ приводит к преобразованию содержимого первого массива в строку, #+ в которой отдельные жлементы первого массива разделены пробелом #+ (первый символ из переменной IFS). # If the original elements didn't contain whitespace . . . # If the original array isn't subscript sparse . . . # Then we could get the original array structure back again. # Restore from the modified second element. echo echo '- - Listing restored element - -' declare -a subArray=( ${dest[1]} ) cnt=${#subArray[@]} echo "Number of elements: $cnt" for (( i = 0 ; i < cnt ; i++ )) do echo "Element [$i]: ${subArray[$i]}" done echo '- - Do not depend on this behavior. - -' echo '- - This behavior is subject to change - -' echo '- - in versions of Bash newer than version 2.05b - -' # MSZ: Sorry about any earlier confusion folks. exit 0
--
Массивы допускают перенос хорошо известных алгоритмов в сценарии на языке командной оболочки. Хорошо ли это -- решать вам.
Пример 25-11. Старая, добрая: "Пузырьковая" сортировка
#!/bin/bash # bubble.sh: "Пузырьковая" сортировка. # На каждом проходе по сортируемому массиву, #+ сравниваются два смежных элемента, и, если необходимо, они меняются местами. # В конце первого прохода, самый "тяжелый" элемент "опускается" в конец массива. # В конце второго прохода, следующий по "тяжести" элемент занимает второе место снизу. # И так далее. # Каждый последующий проход требует на одно сравнение меньше предыдущего. # Поэтому вы должны заметить ускорение работы сценария на последних проходах. exchange() { # Поменять местами два элемента массива. local temp=${Countries[$1]} # Временная переменная Countries[$1]=${Countries[$2]} Countries[$2]=$temp return } declare -a Countries # Объявление массива, #+ необязательно, поскольку он явно инициализируется ниже. # Допустимо ли выполнять инициализацию массива в нескольки строках? # ДА! Countries=(Нидерланды Украина Заир Турция Россия Йемен Сирия \ Бразилия Аргентина Никарагуа Япония Мексика Венесуэла Греция Англия \ Израиль Перу Канада Оман Дания Уэльс Франция Кения \ Занаду Катар Лихтенштейн Венгрия) # "Занаду" -- это мифическое государство, где, согласно Coleridge, #+ Kubla Khan построил величественный дворец. clear # Очистка экрана. echo "0: ${Countries[*]}" # Список элементов несортированного массива. number_of_elements=${#Countries[@]} let "comparisons = $number_of_elements - 1" count=1 # Номер прохода. while [ "$comparisons" -gt 0 ] # Начало внешнего цикла do index=0 # Сбросить индекс перед началом каждого прохода. while [ "$index" -lt "$comparisons" ] # Начало внутреннего цикла do if [ ${Countries[$index]} \> ${Countries[`expr $index + 1`]} ] # Если элементы стоят не по порядку... # Оператор \> выполняет сравнение ASCII-строк #+ внутри одиночных квадратных скобок. # if [[ ${Countries[$index]} > ${Countries[`expr $index + 1`]} ]] #+ дает тот же результат. then exchange $index `expr $index + 1` # Поменять местами. fi let "index += 1" done # Конец внутреннего цикла let "comparisons -= 1" # Поскольку самый "тяжелый" элемент уже "опустился" на дно, #+ то на каждом последующем проходе нужно выполнять на одно сравнение меньше. echo echo "$count: ${Countries[@]}" # Вывести содержимое массива после каждого прохода. echo let "count += 1" # Увеличить счетчик проходов. done # Конец внешнего цикла exit 0
--
Можно ли вложить один массив в другой?
#!/bin/bash # "Вложенный" массив. # Автор: Michael Zick. #+ незначительные изменения и комментарии добавил William Park. AnArray=( $(ls --inode --ignore-backups --almost-all \ --directory --full-time --color=none --time=status \ --sort=time -l ${PWD} ) ) # Команды и опции. # Пробелы важны . . . SubArray=( ${AnArray[@]:11:1} ${AnArray[@]:6:5} ) # Этот массив содержит шесть элементов: #+ SubArray=( [0]=${AnArray[11]} [1]=${AnArray[6]} [2]=${AnArray[7]} # [3]=${AnArray[8]} [4]=${AnArray[9]} [5]=${AnArray[10]} ) # # Массивы в Bash оформляются в виде связанных (циклических) списков #+ где каждый элемент списка имеет тип string (char *). # Таким образом, вложенные массивы фактически таковыми не являются, #+ хотя функционально очень похожи на них. echo "Текущий каталог и дата последнего изменения:" echo "${SubArray[@]}" exit 0
--
Вложенные массивы, в комбинации с косвенными ссылками, предоставляют в распоряжение программиста ряд замечательных возможностей
Пример 25-12. Вложенные массивы и косвенные ссылки
#!/bin/bash # embedded-arrays.sh # Вложенные массивы и косвенные ссылки. # Автор: Dennis Leeuw. # Используется с его разрешения. # Дополнен автором документа. ARRAY1=( VAR1_1=value11 VAR1_2=value12 VAR1_3=value13 ) ARRAY2=( VARIABLE="test" STRING="VAR1=value1 VAR2=value2 VAR3=value3" ARRAY21=${ARRAY1[*]} ) # Вложение массива ARRAY1 в массив ARRAY2. function print () { OLD_IFS="$IFS" IFS=$'\n' # Вывод каждого элемента массива #+ в отдельной строке. TEST1="ARRAY2[*]" local ${!TEST1} # Посмотрите, что произойдет, если убрать эту строку. # Косвенная ссылка. # Позволяет получить доступ к компонентам $TEST1 #+ в этой функции. # Посмотрим, что получилось. echo echo "\$TEST1 = $TEST1" # Просто имя переменной. echo; echo echo "{\$TEST1} = ${!TEST1}" # Вывод на экран содержимого переменной. # Это то, что дает #+ косвенная ссылка. echo echo "-------------------------------------------"; echo echo # Вывод переменной echo "Переменная VARIABLE: $VARIABLE" # Вывод элементов строки IFS="$OLD_IFS" TEST2="STRING[*]" local ${!TEST2} # Косвенная ссылка (то же, что и выше). echo "Элемент VAR2: $VAR2 из строки STRING" # Вывод элемента массива TEST2="ARRAY21[*]" local ${!TEST2} # Косвенная ссылка. echo "Элемент VAR1_1: $VAR1_1 из массива ARRAY21" } print echo exit 0
--
С помощью массивов, на языке командной оболочки, вполне возможно реализовать алгоритм Решета Эратосфена. Конечно же -- это очень ресурсоемкая задача. В виде сценария она будет работать мучительно долго, так что лучше всего реализовать ее на каком либо другом, компилирующем, языке программирования, таком как C.
Пример 25-13. Пример реализации алгоритма Решето Эратосфена
#!/bin/bash # sieve.sh # Решето Эратосфена # Очень старый алгоритм поиска простых чисел. # Этот сценарий выполняется во много раз медленнее # чем аналогичная программа на C. LOWER_LIMIT=1 # Начиная с 1. UPPER_LIMIT=1000 # До 1000. # (Вы можете установить верхний предел и выше... если вам есть чем себя занять.) PRIME=1 NON_PRIME=0 declare -a Primes # Primes[] -- массив. initialize () { # Инициализация массива. i=$LOWER_LIMIT until [ "$i" -gt "$UPPER_LIMIT" ] do Primes[i]=$PRIME let "i += 1" done # Все числа в заданном диапазоне считать простыми, # пока не доказано обратное. } print_primes () { # Вывод индексов элементов массива Primes[], которые признаны простыми. i=$LOWER_LIMIT until [ "$i" -gt "$UPPER_LIMIT" ] do if [ "${Primes[i]}" -eq "$PRIME" ] then printf "%8d" $i # 8 пробелов перед числом придают удобочитаемый табличный вывод на экран. fi let "i += 1" done } sift () # Отсеивание составных чисел. { let i=$LOWER_LIMIT+1 # Нам известно, что 1 -- это простое число, поэтому начнем с 2. until [ "$i" -gt "$UPPER_LIMIT" ] do if [ "${Primes[i]}" -eq "$PRIME" ] # Не следует проверять вторично числа, которые уже признаны составными. then t=$i while [ "$t" -le "$UPPER_LIMIT" ] do let "t += $i " Primes[t]=$NON_PRIME # Все числа, которые делятся на $t без остатка, пометить как составные. done fi let "i += 1" done } # Вызов функций. initialize sift print_primes # Это называется структурным программированием. echo exit 0 # ----------------------------------------------- # # Код, приведенный ниже, не исполняется из-за команды exit, стоящей выше. # Улучшенная версия, предложенная Stephane Chazelas, # работает несколько быстрее. # Должен вызываться с аргументом командной строки, определяющем верхний предел. UPPER_LIMIT=$1 # Из командной строки. let SPLIT=UPPER_LIMIT/2 # Рассматривать делители только до середины диапазона. Primes=( '' $(seq $UPPER_LIMIT) ) i=1 until (( ( i += 1 ) > SPLIT )) # Числа из верхней половины диапазона могут не рассматриваться. do if [[ -n $Primes[i] ]] then t=$i until (( ( t += i ) > UPPER_LIMIT )) do Primes[t]= done fi done echo ${Primes[*]} exit 0
Сравните этот сценарий с генератором простых чисел, не использующим массивов, Пример A-18.
--
Массивы позволяют эмулировать некоторые структуры данных, поддержка которых в Bash не предусмотрена.
Пример 25-14. Эмуляция структуры "СТЕК" ("первый вошел -- последний вышел")
#!/bin/bash # stack.sh: Эмуляция структуры "СТЕК" ("первый вошел -- последний вышел") # Подобно стеку процессора, этот "стек" сохраняет и возвращает данные по принципу #+ "первый вошел -- последний вышел". BP=100 # Базовый указатель на массив-стек. # Дно стека -- 100-й элемент. SP=$BP # Указатель вершины стека. # Изначально -- стек пуст. Data= # Содержимое вершины стека. # Следует использовать дополнительную переменную, #+ из-за ограничений на диапазон возвращаемых функциями значений. declare -a stack push() # Поместить элемент на вершину стека. { if [ -z "$1" ] # А вообще, есть что помещать на стек? then return fi let "SP -= 1" # Переместить указатель стека. stack[$SP]=$1 return } pop() # Снять элемент с вершины стека. { Data= # Очистить переменную. if [ "$SP" -eq "$BP" ] # Стек пуст? then return fi # Это предохраняет от выхода SP за границу стека -- 100, Data=${stack[$SP]} let "SP += 1" # Переместить указатель стека. return } status_report() # Вывод вспомогательной информации. { echo "-------------------------------------" echo "ОТЧЕТ" echo "Указатель стека SP = $SP" echo "Со стека был снят элемент \""$Data"\"" echo "-------------------------------------" echo } # ======================================================= # А теперь позабавимся. echo # Попробуем вытолкнуть что-нибудь из пустого стека. pop status_report echo push garbage pop status_report # Втолкнуть garbage, вытолкнуть garbage. value1=23; push $value1 value2=skidoo; push $value2 value3=FINAL; push $value3 pop # FINAL status_report pop # skidoo status_report pop # 23 status_report # Первый вошел -- последний вышел! # Обратите внимание как изменяется указатель стека на каждом вызове функций push и pop. echo # ======================================================= # Упражнения: # ----------- # 1) Измените функцию "push()" таким образом, # + чтобы она позволяла помещать на стек несколько значений за один вызов. # 2) Измените функцию "pop()" таким образом, # + чтобы она позволяла снимать со стека несколько значений за один вызов. # 3) Попробуйте написать простейший калькулятор, выполняющий 4 арифметических действия? # + используя этот пример. exit 0
--
Иногда, манипуляции с "индексами" массивов могут потребовать введения переменных для хранения промежуточных результатов. В таких случаях вам предоставляется лишний повод подумать о реализации проекта на более мощном языке программирования, например Perl или C.
Пример 25-15. Исследование математических последовательностей
#!/bin/bash # Пресловутая "Q-последовательность" Дугласа Хольфштадтера *Douglas Hofstadter): # Q(1) = Q(2) = 1 # Q(n) = Q(n - Q(n-1)) + Q(n - Q(n-2)), для n>2 # Это "хаотическая" последовательность целых чисел с непредсказуемым поведением. # Первые 20 членов последовательности: # 1 1 2 3 3 4 5 5 6 6 6 8 8 8 10 9 10 11 11 12 # См. книгу Дугласа Хольфштадтера, "Goedel, Escher, Bach: An Eternal Golden Braid", # p. 137, ff. LIMIT=100 # Найти первые 100 членов последовательности LINEWIDTH=20 # Число членов последовательности, выводимых на экран в одной строке Q[1]=1 # Первые два члена последовательности равны 1. Q[2]=1 echo echo "Q-последовательность [первые $LIMIT членов]:" echo -n "${Q[1]} " # Вывести первые два члена последовательности. echo -n "${Q[2]} " for ((n=3; n <= $LIMIT; n++)) # C-подобное оформление цикла. do # Q[n] = Q[n - Q[n-1]] + Q[n - Q[n-2]] для n>2 # Это выражение необходимо разбить на отдельные действия, # поскольку Bash не очень хорошо поддерживает сложные арифметические действия над элементами массивов. let "n1 = $n - 1" # n-1 let "n2 = $n - 2" # n-2 t0=`expr $n - ${Q[n1]}` # n - Q[n-1] t1=`expr $n - ${Q[n2]}` # n - Q[n-2] T0=${Q[t0]} # Q[n - Q[n-1]] T1=${Q[t1]} # Q[n - Q[n-2]] Q[n]=`expr $T0 + $T1` # Q[n - Q[n-1]] + Q[n - Q[n-2]] echo -n "${Q[n]} " if [ `expr $n % $LINEWIDTH` -eq 0 ] # Если выведено очередные 20 членов в строке. then # то echo # перейти на новую строку. fi done echo exit 0 # Этот сценарий реализует итеративный алгоритм поиска членов Q-последовательности. # Рекурсивную реализацию, как более интуитивно понятную, оставляю вам, в качестве упражнения. # Внимание: рекурсивный поиск членов последовательности будет занимать *очень* продолжительное время.
--
Bash поддерживает только одномерные массивы, но, путем небольших ухищрений, можно эмулировать многомерные массивы.
Пример 25-16. Эмуляция массива с двумя измерениями
#!/bin/bash # Эмуляция двумерного массива. # Второе измерение представлено как последовательность строк. Rows=5 Columns=5 declare -a alpha # char alpha [Rows] [Columns]; # Необязательное объявление массива. load_alpha () { local rc=0 local index for i in A B C D E F G H I J K L M N O P Q R S T U V W X Y do local row=`expr $rc / $Columns` local column=`expr $rc % $Rows` let "index = $row * $Rows + $column" alpha[$index]=$i # alpha[$row][$column] let "rc += 1" done # Более простой вариант # declare -a alpha=( A B C D E F G H I J K L M N O P Q R S T U V W X Y ) # но при таком объявлении второе измерение массива завуалировано. } print_alpha () { local row=0 local index echo while [ "$row" -lt "$Rows" ] # Вывод содержимого массива построчно do local column=0 while [ "$column" -lt "$Columns" ] do let "index = $row * $Rows + $column" echo -n "${alpha[index]} " # alpha[$row][$column] let "column += 1" done let "row += 1" echo done # Более простой эквивалент: # echo ${alpha[*]} | xargs -n $Columns echo } filter () # Отфильтровывание отрицательных индексов. { echo -n " " if [[ "$1" -ge 0 && "$1" -lt "$Rows" && "$2" -ge 0 && "$2" -lt "$Columns" ]] then let "index = $1 * $Rows + $2" echo -n " ${alpha[index]}" # alpha[$row][$column] fi } rotate () # Поворот массива на 45 градусов { local row local column for (( row = Rows; row > -Rows; row-- )) # В обратном порядке. do for (( column = 0; column < Columns; column++ )) do if [ "$row" -ge 0 ] then let "t1 = $column - $row" let "t2 = $column" else let "t1 = $column" let "t2 = $column + $row" fi filter $t1 $t2 # Отфильтровать отрицательный индекс. done echo; echo done # Поворот массива выполнен на основе примеров (стр. 143-146) # из книги "Advanced C Programming on the IBM PC", автор Herbert Mayer # (см. библиографию). } #-----------------------------------------------------# load_alpha # Инициализация массива. print_alpha # Вывод на экран. rotate # Повернуть на 45 градусов против часовой стрелки. #-----------------------------------------------------# # Упражнения: # ----------- # 1) Сделайте инициализацию и вывод массива на экран # + более простым и элегантным способом. # # 2) Объясните принцип работы функции rotate(). exit 0
По существу, двумерный массив эквивалентен одномерному, с тем лишь различием, что для индексации отдельных элементов используются два индекса -- "строка" и "столбец".
Более сложный пример эмуляции двумерного массива вы найдете в Пример A-11.
Назад | В начало документа | Вперед |
Списки команд | К началу раздела | Файлы |