Искусство программирования на языке сценариев командной оболочки - Мендель Купер
Шрифт:
Интервал:
Закладка:
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.
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} )
# Массив имеет два элемента, каждый из которых, в свою очередь, является массивом.
echo "Текущий каталог и дата последнего изменения:"
echo "${SubArray[@]}"
exit 0
--
Вложенные массивы, в комбинации с косвенными ссылками, предоставляют в распоряжение программиста ряд замечательных возможностей
Пример 25-7. Вложенные массивы и косвенные ссылки
#!/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"
}
echo
exit 0
--
С помощью массивов, на языке командной оболочки, вполне возможно реализовать алгоритм Решета Эратосфена. Конечно же -- это очень ресурсоемкая задача. В виде сценария она будет работать мучительно долго, так что лучше всего реализовать ее на каком либо другом, компилирующем, языке программирования, таком как C.
Пример 25-8. Пример реализации алгоритма Решето Эратосфена
#!/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-9. Эмуляция структуры "СТЕК" ("первый вошел -- последний вышел")
#!/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