П О Р Т А Л                            
С Е Т Е В Ы Х                          
П Р О Е К Т О В                        
  
Поиск по сайту:
                                                 
Главная

О проекте

Web-мастеру
     HTML & JavaScript
     SSI
     Perl
     PHP
     XML & XSLT
     Unix Shell

MySQL

Безопасность

Хостинг

Другое








Самое читаемое:

Учебник PHP - "Для Чайника".
Просмотров 3503 раз(а).

Иллюстрированный самоучитель по созданию сайтов.
Просмотров 6104 раз(а).

Учебник HTML.
Просмотров 3267 раз(а).

Руководство по PHP5.
Просмотров 5486 раз(а).

Хостинг через призму DNS.
Просмотров 4125 раз(а).

Подборка текстов стандартных документов.
Просмотров 55763 раз(а).

Учебник PHP - Самоучитель
Просмотров 3079 раз(а).

Документация на MySQL (учебник & справочное руководство)
Просмотров 5491 раз(а).

Внешние атаки...
Просмотров 3824 раз(а).

Учебник PHP.
Просмотров 2819 раз(а).

SSI в примерах.
Просмотров 37454 раз(а).



 
 
| Добавить в избранное | Сделать стартовой | Помощь





Глава 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
Note

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.


Tip

Операция подстановки команд -- 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
Note

Явное объявление массива с помощью конструкции 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.

Назад | Вперед
Содержание (общее) | Содержание раздела | Содержание подраздела



Если Вы не нашли что искали, то рекомендую воспользоваться поиском по сайту: