Искусство программирования на языке сценариев командной оболочки

         

Пример 29-5. Ловушка на выходе

#!/bin/bash

trap 'echo Список переменных --- a = $a b = $b' EXIT # EXIT -- это название сигнала, генерируемого при выходе из сценария.

a=39

b=36

exit 0 # Примечательно, что если закомментировать команду 'exit', # то это никак не скажется на работе сценария, # поскольку "выход" из сценария происходит в любом случае.

Пример 29-6. Удаление временного файла при нажатии на Control-C

#!/bin/bash # logon.sh: Сценарий, написаный "на скорую руку", контролирует вход в режим on-line.

TRUE=1 LOGFILE=/var/log/messages # Обратите внимание: $LOGFILE должен быть доступен на чтение (chmod 644 /var/log/messages). TEMPFILE=temp.$$ # "Уникальное" имя для временного файла, где расширение в имени -- это pid процесса-сценария.


KEYWORD=address # При входе, в файл /var/log/messages, # добавляется строка "remote IP address xxx.xxx.xxx.xxx" ONLINE=22 USER_INTERRUPT=13 CHECK_LINES=100 # Количество проверяемых строк.

trap 'rm -f $TEMPFILE; exit $USER_INTERRUPT' TERM INT # Удалить временный файл, когда сценарий завершает работу по control-c.

echo

while [ $TRUE ] #Бесконечный цикл. do tail -$CHECK_LINES $LOGFILE> $TEMPFILE # Последние 100 строк из системного журнала переписать во временный файл. # Совершенно необходимо, т.к. новейшие версии ядер генерируют много сообщений при входе. search=`grep $KEYWORD $TEMPFILE` # Проверить наличие фразы "address", # свидетельствующей об успешном входе.

if [ ! -z "$search" ] # Кавычки необходимы, т.к. переменная может содержать пробелы. then echo "On-line" rm -f $TEMPFILE # Удалить временный файл. exit $ONLINE else echo -n "." # ключ -n подавляет вывод символа перевода строки, # так вы получите непрерывную строку точек. fi

sleep 1 done

# Обратите внимание: если изменить содержимое переменной KEYWORD # на "Exit", то сценарий может использоваться для контроля # неожиданного выхода (logoff).

exit 0

# Nick Drage предложил альтернативный метод:

while true do ifconfig ppp0 | grep UP 1> /dev/null && echo "соединение установлено" && exit 0 echo -n "." # Печать последовательности точек (.....), пока соединение не будет установлено. sleep 2 done

# Проблема: Нажатия Control-C может оказаться недостаточным, чтобы завершить этот процесс. # (Точки продолжают выводиться на экран.) # Упражнение: Исправьте этот недостаток.



# Stephane Chazelas предложил еще одну альтернативу:

CHECK_INTERVAL=1

while ! tail -1 "$LOGFILE" | grep -q "$KEYWORD" do echo -n . sleep $CHECK_INTERVAL done echo "On-line"

# Упражнение: Найдите сильные и слабые стороны # каждого из этих подходов.

Аргумент DEBUG, команды trap, заставляет сценарий выполнять указанное действие после выполнения каждой команды. Это можно использовать для трассировки переменных.

Пример 29-7. Трассировка переменной

#!/bin/bash

trap 'echo "VARIABLE-TRACE> $LINENO: \$variable = \"$variable\""' DEBUG # Выводить значение переменной после исполнения каждой команды.

variable=29

echo "Переменная \"\$variable\" инициализирована числом $variable."

let "variable *= 3" echo "Значение переменной \"\$variable\" увеличено в 3 раза."

# Конструкция "trap 'commands' DEBUG" может оказаться очень полезной # при отладке больших и сложных скриптов, # когда размещение множества инструкций "echo $variable" # может потребовать достаточно большого времени.

# Спасибо Stephane Chazelas.

exit 0

<


Конструкция trap '' SIGNAL (две одиночных кавычки) -- запрещает SIGNAL для оставшейся части сценария. Конструкция trap SIGNAL

-- восстанавливает действие сигнала SIGNAL. Эти конструкции могут использоваться для защиты критических участков сценария от нежелательного прерывания.

trap '' 2 # Сигнал 2 (Control-C) -- запрещен. command command command trap 2 # Разрешение реакции на Control-C

Глава 30. Необязательные параметры (ключи)

Необязательные параметры -- это дополнительные ключи (опции), которые оказывают влияние на поведение сценария и/или командной оболочки.

Команда set позволяет задавать дополнительные опции прямо внутри сценария. В том месте сценария, где необходимо, чтобы та или иная опция вступила в силу, вставьте такую конструкцию set -o option-name, или в более короткой форме -- set -option-abbrev. Эти две формы записи совершенно идентичны по своему действию.

#!/bin/bash

set -o verbose # Вывод команд перед их исполнением.

#!/bin/bash

set -v # Имеет тот же эффект, что и выше.

Для того, чтобы отключить действие той или иной опции, следует вставить конструкцию set +o option-name, или set +option-abbrev.

#!/bin/bash

set -o verbose # Вывод команд перед их исполнением. command ... command

set +o verbose # Запретить вывод команд перед их исполнением. command # команда не выводится.

set -v # Вывод команд перед их исполнением. command ... command

set +v # Запретить вывод команд перед их исполнением. command

exit 0

Как вариант установки опций, можно предложить указывать их в заголовке сценария (в строке sha-bang) -- #!.

#!/bin/bash -x # # Далее следует текст сценария.

Так же можно указывать дополнительные ключи в командной строке, при запуске сценария. Некоторые из опций работают только если они заданы из командной строки, например -i -- ключ интерактивного режима работы скрипта.

bash -v script-name

bash -o verbose script-name

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



Таблица 30-1. Ключи Bash

Краткое имя

Полное имя

Описание

-C noclobber Предотвращает перезапись файла в операциях перенаправления вывода (не распространяется на конвейеры (каналы) -- >|)
-D (нет) Выводит список строк в двойных кавычках, которым предшествует символ $, сам сценарий не исполняется
-a allexport Экспорт всех, определенных в сценарии, переменных
-b notify Выводит уведомление по завершении фоновой задачи (job) (довольно редко используется в сценариях)
-c ... (нет) Читает команды из ...
-f noglob Подстановка имен файлов (globbing) запрещена
-i interactive Сценарий запускается в интерактивном

режиме
-p privileged Сценарий запускается как "suid" (осторожно!)
-r restricted Сценарий запускается в ограниченном режиме (см. Глава 20).
-u nounset При попытке обращения к неопределенным переменным, выдает сообщение об ошибке и прерывает работу сценария
-v verbose Выводит на stdout каждую команду прежде, чем она будет исполнена
-x xtrace Подобна -v, но выполняет подстановку команд
-e errexit Прерывает работу сценария при появлении первой же ошибки (когда команда возвращает ненулевой код завершения)
-n noexec Читает команды из сценария, но не исполняет их (проверка синтаксиса)
-s stdin Читает команды с устройства stdin
-t (нет) Выход после исполнения первой команды
- (нет) Конец списка ключей (опций), последующие аргументы будут восприниматься как позиционные параметры.
-- (нет) Эквивалент предыдущей опции (-).
Глава 31. Широко распространенные ошибки

  Turandot: Gli enigmi sono tre, la morte una!

Caleph: No, no! Gli enigmi sono tre, una la vita!

  Puccini
Использование зарезервированных слов и служебных символов в качестве имен переменных.

case=value0 # Может вызвать проблемы. 23skidoo=value1 # Тоже самое. # Имена переменных, начинающиеся с цифр, зарезервированы командной оболочкой. # Если имя переменной начинается с символа подчеркивания: _23skidoo=value1, то это не считается ошибкой.



# Однако... если имя переменной состоит из единственного символа подчеркивания, то это ошибка. _=25 echo $_ # $_ -- это внутренняя переменная.

xyz((!*=value2 # Вызывает серьезные проблемы.

Использование дефиса, и других зарезервированных символов, в именах переменных.

var-1=23 # Вместо такой записи используйте 'var_1'.

Использование одинаковых имен для переменных и функций. Это делает сценарий трудным для понимания.

do_something () { echo "Эта функция должна что-нибудь сделать с \"$1\"." }

do_something=do_something

do_something do_something

# Все это будет работать правильно, но слишком уж запутанно.

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

var1 = 23 # Правильный вариант: 'var1=23'. # В вышеприведенной строке Bash будет трактовать "var1" как имя команды # с аргументами "=" и "23".

let c = $a - $b # Правильный вариант: 'let c=$a-$b' или 'let "c = $a - $b"'

if [ $a -le 5] # Правильный вариант: if [ $a -le 5 ] # if [ "$a" -le 5 ] еще лучше. # [[ $a -le 5 ]] тоже верно.

Ошибочным является предположение о том, что неинициализированные переменные содержат "ноль". Неинициализированные переменные содержат "пустое" (null) значение, а не ноль.

#!/bin/bash

echo "uninitialized_var = $uninitialized_var" # uninitialized_var =

Часто программисты путают операторы сравнения = и -eq. Запомните, оператор = используется для сравнения строковых переменных, а -eq -- для сравнения целых чисел.

if [ "$a" = 273 ] # Как вы полагаете? $a -- это целое число или строка? if [ "$a" -eq 273 ] # Если $a -- целое число.

# Иногда, такого рода ошибка никак себя не проявляет. # Однако...

a=273.0 # Не целое число.

if [ "$a" = 273 ] then echo "Равны." else echo "Не равны." fi # Не равны.

# тоже самое и для a=" 273" и a="0273".

# Подобные проблемы возникают при использовании "-eq" со строковыми значениями.



if [ "$a" -eq 273.0 ] then echo "a = $a' fi # Исполнение сценария прерывается по ошибке. # test.sh: [: 273.0: integer expression expected

Ошибки при сравнении целых чисел и строковых значений.

#!/bin/bash # bad-op.sh

number=1

while [ "$number" < 5 ] # Неверно! должно быть while [ "number" -lt 5 ] do echo -n "$number " let "number += 1" done

# Этот сценарий генерирует сообщение об ошибке: # bad-op.sh: 5: No such file or directory

Иногда, в операциях проверки, с использованием квадратных скобок ([ ]), переменные необходимо брать в двойные кавычки. См. Пример 7-6, Пример 16-4 и Пример 9-6.

Иногда сценарий не в состоянии выполнить команду из-за нехватки прав доступа. Если пользователь не сможет запустить команду из командной строки, то эта команда не сможет быть запущена и из сценария. Попробуйте изменить атрибуты команды, возможно вам придется установить бит suid.

Использование символа - в качестве оператора перенаправления (каковым он не является) может приводить к неожиданным результатам.

command1 2> - | command2 # Попытка передать сообщения об ошибках команде command1 через конвейер... # ...не будет работать.

command1 2>& - | command2 # Так же бессмысленно.

Спасибо S.C.

Использование функциональных особенностей Bash версии 2 или выше, может привести к аварийному завершению сценария, работающему под управлением Bash версии 1.XX.

#!/bin/bash

minimum_version=2 # Поскольку Chet Ramey постоянно развивает Bash, # вам может потребоваться указать другую минимально допустимую версию $minimum_version=2.XX. E_BAD_VERSION=80

if [ "$BASH_VERSION" \< "$minimum_version" ] then echo "Этот сценарий должен исполняться под управлением Bash, версии $minimum или выше." echo "Настоятельно рекомендуется обновиться." exit $E_BAD_VERSION fi

...

Использование специфических особенностей Bash может приводить к аварийному завершению сценария в Bourne shell (#!/bin/sh).


Как правило, в Linux дистрибутивах, sh является псевдонимом bash, но это не всегда верно для UNIX-систем вообще.

Сценарий, в котором строки отделяются друг от друга в стиле MS-DOS (\r\n), будет завершаться аварийно, поскольку комбинация #!/bin/bash\r\n

считается недопустимой. Исправить эту ошибку можно простым удалением символа \r из сценария.

#!/bin/bash

echo "Начало"

unix2dos $0 # Сценарий переводит символы перевода строки в формат DOS. chmod 755 $0 # Восстановление прав на запуск. # Команда 'unix2dos' удалит право на запуск из атрибутов файла.

./$0 # Попытка запустить себя самого. # Но это не сработает из-за того, что теперь строки отделяются # друг от друга в стиле DOS.

echo "Конец"

exit 0

Сценарий, начинающийся с #!/bin/sh, не может работать в режиме полной совместимости с Bash. Некоторые из специфических функций, присущих Bash, могут оказаться запрещенными к использованию. Сценарий, который требует полного доступа ко всем расширениям, имеющимся в Bash, должен начинаться строкой #!/bin/bash.

Сценарий не может экспортировать переменные родительскому процессу - оболочке. Здесь как в природе, потомок может унаследовать черты родителя, но не наооборот.

WHATEVER=/home/bozo export WHATEVER exit 0

bash$ echo $WHATEVER

bash$

Будьте уверены -- при выходе в командную строку переменная $WHATEVER останется неинициализированной.

Использование в подоболочке переменных с теми же именами, что и в родительской оболочке может не давать ожидаемого результата.

Пример 31-1. Западня в подоболочке

#!/bin/bash # Западня в подоболочке.

outer_variable=внешняя_переменная echo echo "outer_variable = $outer_variable" echo

( # Запуск в подоболочке

echo "внутри подоболочки outer_variable = $outer_variable" inner_variable=внутренняя_переменная # Инициализировать echo "внутри подоболочки inner_variable = $inner_variable" outer_variable=внутренняя_переменная # Как думаете? Изменит внешнюю переменную? echo "внутри подоболочки outer_variable = $outer_variable"



# Выход из подоболочки )

echo echo "за пределами подоболочки inner_variable = $inner_variable" # Ничего не выводится. echo "за пределами подоболочки outer_variable = $outer_variable" # внешняя_переменная. echo

exit 0

Передача вывода от echo по конвейеру команде read может давать неожиданные результаты. В этом сценарии, команда read действует так, как будто бы она была запущена в подоболочке. Вместо нее лучше использовать команду set (см. Пример 11-14).

Пример 31-2. Передача вывода от команды echo команде read, по конвейеру

#!/bin/bash # badread.sh: # Попытка использования 'echo' и 'read' #+ для записи значений в переменные.

a=aaa b=bbb c=ccc

echo "один два три" | read a b c # Попытка записать значения в переменные a, b и c.

echo echo "a = $a" # a = aaa echo "b = $b" # b = bbb echo "c = $c" # c = ccc # Присваивания не произошло.

# ------------------------------

# Альтернативный вариант.

var=`echo "один два три"` set -- $var a=$1; b=$2; c=$3

echo "-------" echo "a = $a" # a = один echo "b = $b" # b = два echo "c = $c" # c = три # На этот раз все в порядке.

# ------------------------------

# Обратите внимание: в подоболочке 'read', для первого варианта, переменные присваиваются нормально. # Но только в подоболочке.

a=aaa # Все сначала. b=bbb c=ccc

echo; echo echo "один два три" | ( read a b c; echo "Внутри подоболочки: "; echo "a = $a"; echo "b = $b"; echo "c = $c" ) # a = один # b = два # c = три echo "-------" echo "Снаружи: " echo "a = $a" # a = aaa echo "b = $b" # b = bbb echo "c = $c" # c = ccc echo

exit 0

Огромный риск, для безопасности системы, представляет использование в скриптах команд, с установленным битом "suid". [61]

Использование сценариев в качестве CGI-приложений может приводить к серьезным проблемам из-за отсутствия контроля типов переменных.


Более того, они легко могут быть заменены взломщиком на его собственные сценарии.

Bash не совсем корректно обрабатывает строки, содержащие двойной слэш (//).

Сценарии на языке Bash, созданные для Linux или BSD систем, могут потребовать доработки, перед тем как они смогут быть запущены в коммерческой версии UNIX. Такие сценарии, как правило, используют GNU-версии команд и утилит, которые имеют лучшую функциональность, нежели их аналоги в UNIX. Это особенно справедливо для таких утилит обработки текста, как tr.

  Danger is near thee --

Beware, beware, beware, beware.

Many brave hearts are asleep in the deep.

So beware --

Beware.

  A.J. Lamb and H.W. Petrie
Глава 32. Стиль программирования

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

Ниже приводится несколько рекомендаций по оформлению сценариев, однако их не следует рассматривать как Официальное Руководство.

32.1. Неофициальные рекомендации по оформлению сценариев

Комментируйте свой код. Это сделает ваши сценарии понятнее для других, и более простыми, в обслуживании, для вас.

PASS="$PASS${MATRIX:$(($RANDOM%${#MATRIX})):1}" # Эта строка имела некоторый смысл в момент написания, # но через год-другой будет очень тяжело вспомнить -- что она делает. # (Из сценария "pw.sh", автор: Antek Sawicki)

Добавляйте заголовочные комментарии в начале сценария и перед функциями.

#!/bin/bash

#************************************************# # xyz.sh # # автор: Bozo Bozeman # # Июль 05, 2001 # # # # Удаление файлов проекта. # #************************************************#

BADDIR=65 # Нет такого каталога. projectdir=/home/bozo/projects # Каталог проекта.

# ------------------------------------------------------- # # cleanup_pfiles () # # Удаляет все файлы в заданном каталоге. # # Параметры: $target_directory # # Возвращаемое значение: 0 -- в случае успеха, # # $BADDIR -- в случае ошибки. # # ------------------------------------------------------- # cleanup_pfiles () { if [ ! -d "$1" ] # Проверка существования заданного каталога.


then echo "$1 -- не является каталогом." return $BADDIR fi

rm -f "$1"/* return 0 # Успешное завершение функции. }

cleanup_pfiles $projectdir

exit 0

Не забывайте начинать ваш сценарий с sha-bang -- #!/bin/bash.

Заменяйте повторяющиеся значения константами. Это сделает ваш сценарий более простым для понимания и позволит вносить изменения, не опасаясь за его работоспособность.

if [ -f /var/log/messages ] then ... fi # Представьте себе, что через пару лет # вы захотите изменить /var/log/messages на /var/log/syslog. # Тогда вам придется отыскать все строки, # содержащие /var/log/messages, и заменить их на /var/log/syslog. # И проверить несколько раз -- не пропустили ли что-нибудь.

# Использование "констант" дает лучший способ: LOGFILE=/var/log/messages # Если и придется изменить, то только в этой строке. if [ -f "$LOGFILE" ] then ... fi

В качестве имен переменных и функций выбирайте осмысленные названия.

fl=`ls -al $dirname` # Не очень удачное имя переменной. file_listing=`ls -al $dirname` # Уже лучше.

MAXVAL=10 # Пишите имена констант в верхнем регистре. while [ "$index" -le "$MAXVAL" ] ...

E_NOTFOUND=75 # Имена кодов ошибок -- в верхнем регистре, # к тому же, их желательно дополнять префиксом "E_". if [ ! -e "$filename" ] then echo "Файл $filename не найден." exit $E_NOTFOUND fi

MAIL_DIRECTORY=/var/spool/mail/bozo # Имена переменных окружения # так же желательно записывать символами # в верхнем регистре. export MAIL_DIRECTORY

GetAnswer () # Смешивание символов верхнего и нижнего решистров # удобно использовать для имен функций. { prompt=$1 echo -n $prompt read answer return $answer }

GetAnswer "Ваше любимое число? " favorite_number=$? echo $favorite_number

_uservariable=23 # Допустимо, но не рекомендуется. # Желательно, чтобы пользовательские переменные не начинались с символа подчеркивания. # Так обычно начинаются системные переменные.

Используйте смысловые имена для кодов завершения.



E_WRONG_ARGS=65 ... ... exit $E_WRONG_ARGS

См. так же Приложение C.

Разделяйте большие сложные сценарии на серию более коротких и простых модулей. Пользуйтесь функциями. См. Пример 34-4.

Не пользуйтесь сложными конструкциями, если их можно заменить простыми.

COMMAND if [ $? -eq 0 ] ... # Избыточно и неинтуитивно.

if COMMAND ... # Более понятно и коротко.

  ... читая исходные тексты сценариев на Bourne shell (/bin/sh). Я был потрясен тем, насколько непонятно и загадочно могут выглядеть очень простые алгоритмы из-за неправильного оформления кода. Я не раз спрашивал себя: "Неужели кто-то может гордиться таким кодом?"

  Landon Noll
Глава 33. Разное

  Практически никто не знает грамматики Bourne shell-а. Даже изучение исходных текстов не дает ее полного понимания.

  Tom Duff
33.1. Интерактивный и неинтерактивный режим работы

В интеракивном режиме, оболочка читает команды, вводимые пользователем, с устройства tty. Кроме того, такая оболочка считывает конфигурационные файлы на запуске, выводит строку приглашения к вводу (prompt), и, по-умолчанию, разрешает управление заданиями. Пользователь имеет возможность взаимодействия с оболочкой.

Сценарий всегда запускается в неинтерактивном режиме. Но, не смотря на это, он сохраняет доступ к своему tty. И даже может эмулировать интерактивный режим работы.

#!/bin/bash MY_PROMPT='$ ' while : do echo -n "$MY_PROMPT" read line eval "$line" done

exit 0

# Этот сценарий, как иллюстрация к вышесказанному, предоставлен # Stephane Chazelas (спасибо).

Будем считать интерактивным такой сценарий, который может принимать ввод от пользователя, обычно с помощью команды read (см. Пример 11-2). В "реальной жизни" все намного сложнее. Пока же, будем придерживаться предположения о том, что интерактивный сценарий ограничен рамками tty, с которого сценарий был запущен пользователемa, т.е консоль или окно xterm.

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


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

Неинтерактивные сценарии прекрасно могут работать в фоне, в то время, как интерактивные -- подвисают, останавливаясь на операциях, ожидающих ввода пользователя. Сложности, возникающие с запуском интерактивных сценариев в фоновом режиме, могут быть преодолены с помощью expect-сценария или встроенного документа. В простейших случаях, можно организовать перенаправление ввода из файла в команду read (read variable <file). Эти приемы позволят создавать сценарии, которые смогут работать как в интерактивном, так и в неинтерактивном режимах.

Если внутри сценария необходимо проверить режим работы -- интерактивный или неинтерактивный, это можно сделать проверкой переменной окружения $PS1.

if [ -z $PS1 ] # интерактивный режим? then # неинтерактивный ... else # интерактивный ... fi

Еще один способ -- проверка установки флага "i" в переменной $-.

case $- in *i*) # интерактивный режим ;; *) # неинтерактивный режим ;; # (Из "UNIX F.A.Q.," 1993)

Сценарий может принудительно запускаться в интерактивном режиме, для этого необходимо указать ключ -i в строке-заголовке #!/bin/bash -i. Однако вы должны помнить о том, что в таких случаях сценарий может выдавать сообщения об ошибках даже тогда, когда ошибок, по сути, нет.

33.2. Сценарии-обертки

"Обертки" -- это сценарии, которые содержат один или несколько вызовов системных команд или утилит, с длинным списком параметров. Такой прием освобождает пользователя от необходимости вводить вручную сложные и длинные команды из командной строки. Он особенно полезен при работе с sed и awk.

Сценарии sed или awk, как правило вызываются в форме: sed -e 'commands'

или awk 'commands'. "Заворачивая" такие вызовы в сценарий на языке командной оболочки, мы делаем их использование более простым для конечного пользователя.


Кроме того, этот прием позволяет комбинировать вызовы sed и awk, например в конвейере, позволяя передавать данные с выхода одной утилиты на вход другой.

Пример 33-1. сценарий-обертка

#!/bin/bash

# Этот простой сценарий удаляет пустые строки из текстового файла. # Проверка входных аргументов не производится. # # Однако вы можете дополнить сценарий такой проверкой, # добавив нечто подобное: # if [ -z "$1" ] # then # echo "Порядок использования: `basename $0` текстовый_файл" # exit 65 # fi

# Для выполнения этих же действий, # из командной строки можно набрать # sed -e '/^$/d' filename

sed -e /^$/d "$1" # '-e' -- означает команду "editing" (правка), за которой следуют необязательные параметры. # '^' -- с начала строки, '$' -- до ее конца. # Что соответствует строкам, которые не содержат символов между началом и концом строки, #+ т.е. -- пустым строкам. # 'd' -- команда "delete" (удалить).

# Использование кавычек дает возможность #+ обрабатывать файлы, чьи имена содержат пробелы.

exit 0

Пример 33-2. Более сложный пример сценария-обертки

#!/bin/bash

# "subst", Сценарий замены по шаблону # т.е., "subst Smith Jones letter.txt".

ARGS=3 E_BADARGS=65 # Неверное число аргументов.

if [ $# -ne "$ARGS" ] # Проверка числа аргументов. then echo "Проядок использования: `basename $0` old-pattern new-pattern filename" exit $E_BADARGS fi

old_pattern=$1 new_pattern=$2

if [ -f "$3" ] then file_name=$3 else echo "Файл \"$3\" не найден." exit $E_BADARGS fi

# Здесь, собственно, выполняется сама работа по поиску и замене. sed -e "s/$old_pattern/$new_pattern/g" $file_name # 's' -- команда "substitute" (замены), # а /pattern/ -- задает шаблон искомого текста. # "g" -- флаг "global" (всеобщий), означает "выполнить подстановку для *каждого* # обнаруженного $old_pattern во всех строках, а не только в первой строке.

exit 0 # При успешном завершении сценария -- вернуть 0.



Пример 33-3. Сценарий-обертка вокруг сценария awk

#!/bin/bash

# Суммирует числа в заданном столбце из заданного файла.

ARGS=2 E_WRONGARGS=65

if [ $# -ne "$ARGS" ] # Проверка числа аргументов. then echo "Порядок использования: `basename $0` имя_файла номер_столбца" exit $E_WRONGARGS fi

filename=$1 column_number=$2

# Здесь используется прием передачи переменных # из командной оболочки в сценарий awk .

# Многострочный сценарий awk должен записываться в виде: awk ' ..... '

# Начало awk-сценария. # ----------------------------- awk '

{ total += $'"${column_number}"' } END { print total }

' "$filename" # ----------------------------- # Конец awk-сценария.

# С точки зрения безопасности, передача shell-переменных # во встроенный awk-скрипт, потенциально опасна, # поэтому, Stephane Chazelas предлагает следующую альтернативу: # --------------------------------------- # awk -v column_number="$column_number" ' # { total += $column_number # } # END { # print total # }' "$filename" # ---------------------------------------

exit 0

Для сценариев, которые должны строиться по принципу швейцарского армейского ножа -- "все в одном", можно порекомендовать Perl. Perl совмещает в себе мощь и гибкость sed, awk и языка программирования C. Он поддерживает модульность и объектно-ориентированный стиль программирования. Короткие сценарии Perl могут легко встраиваться в сценарии командной оболочки, и даже полностью заменить из (хотя автор весьма скептически относится к последнему утверждению).

Пример 33-4. Сценарий на языке Perl, встроенный в Bash-скрипт

#!/bin/bash

# Это команды shell, предшествующий сценарию на Perl. echo "Эта строка выводится средствами Bash, перед выполнением встроенного Perl-скрипта, в \"$0\"." echo "=============================================================================================="

perl -e 'print "Эта строка выводится средствами Perl.\n";' # Подобно sed, Perl тоже использует ключ "-e".



echo "====================================="

exit 0

Допускается даже комбинирование сценариев на Bash и на Perl, в пределах одного файла. В зависимости от того, какая часть сценария должна исполняться, сценарий вызывается с указанием требуемого интерпретатора.

Пример 33-5. Комбинирование сценария Bash и Perl в одном файле

#!/bin/bash # bashandperl.sh

echo "Вас приветствует часть сценария, написанная на Bash." # Далее могут следовать другие команды Bash.

exit 0 # Конец сценария на Bash.

# =======================================================

#!/usr/bin/perl # Эта часть сценария должна вызываться с ключом -x.

print "Вас приветствует часть сценария, написанная на Perl.\n"; # Далее могут следовать другие команды Perl.

# Конец сценария на Perl.

bash$ bash bashandperl.sh

Вас приветствует часть сценария, написанная на Bash.

bash$ perl -x bashandperl.sh

Вас приветствует часть сценария, написанная на Perl.

33.3. Операции сравнения: Альтернативные решения

Операции сравнения, выполняемые с помощью конструкции [[ ]], могут оказаться предпочтительнее, чем [ ]. Аналогично, при сравнении чисел, в более выгодном свете представляется конструкция (( )).

a=8

# Все, приведенные ниже, операции сравнения -- эквивалентны. test "$a" -lt 16 && echo "да, $a < 16" # "И-список" /bin/test "$a" -lt 16 && echo "да, $a < 16" [ "$a" -lt 16 ] && echo "да, $a < 16" [[ $a -lt 16 ]] && echo "да, $a < 16" # Внутри [[ ]] и (( )) переменные (( a < 16 )) && echo "да, $a < 16" # не обязательно брать в кавычки.

city="New York" # Опять же, все, приведенные ниже, операции -- эквивалентны. test "$city" \< Paris && echo "Да, Paris больше, чем $city" # В смысле ASCII-строк. /bin/test "$city" \< Paris && echo "Да, Paris больше, чем $city" [ "$city" \< Paris ] && echo "Да, Paris больше, чем $city" [[ $city < Paris ]] && echo "Да, Paris больше, чем $city" # Кавычки вокруг $city не обязательны.



# Спасибо S.C.

33.4. Рекурсия

Может ли сценарий рекурсивно вызывать себя самого? Да, может!

Пример 33-6. Сценарий (бесполезный), который вызывает себя сам

#!/bin/bash # recurse.sh

# Может ли сценарий вызвать себя сам? # Да, но есть ли в этом смысл?

RANGE=10 MAXVAL=9

i=$RANDOM let "i %= $RANGE" # Генерация псевдослучайного числа в диапазоне 0 .. $MAXVAL.

if [ "$i" -lt "$MAXVAL" ] then echo "i = $i" ./$0 # Сценарий запускает новый экземпляр себя самого. fi # если число $i больше или равно $MAXVAL.

# Если конструкцию "if/then" заменить на цикл "while", то это вызовет определенные проблемы. # Объясните -- почему?.

exit 0

Пример 33-7. Сценарий имеющий практическую ценность), который вызывает себя сам

#!/bin/bash # pb.sh: телефонная книга

# Автор: Rick Boivie # используется с его разрешения. # Дополнен автором документа.

MINARGS=1 # Сценарию должен быть передан, по меньшей мере, один аргумент. DATAFILE=./phonebook PROGNAME=$0 E_NOARGS=70 # Ошибка, нет аргументов.

if [ $# -lt $MINARGS ]; then echo "Порядок использования: "$PROGNAME" data" exit $E_NOARGS fi

if [ $# -eq $MINARGS ]; then grep $1 "$DATAFILE" else ( shift; "$PROGNAME" $* ) | grep $1 # Рекурсивный вызов. fi

exit 0 # Сценарий завершает свою работу здесь. # Далее следует пример файла телефонной книги #+ в котором не используются символы комментария.

# ------------------------------------------------------------------------ # Пример файла телефонной книги

John Doe 1555 Main St., Baltimore, MD 21228 (410) 222-3333 Mary Moe 9899 Jones Blvd., Warren, NH 03787 (603) 898-3232 Richard Roe 856 E. 7th St., New York, NY 10009 (212) 333-4567 Sam Roe 956 E. 8th St., New York, NY 10009 (212) 444-5678 Zoe Zenobia 4481 N. Baker St., San Franciso, SF 94338 (415) 501-1631 # ------------------------------------------------------------------------

$bash pb.sh Roe Richard Roe 856 E. 7th St., New York, NY 10009 (212) 333-4567 Sam Roe 956 E. 8th St., New York, NY 10009 (212) 444-5678



$bash pb. sh Roe Sam Sam Roe 956 E. 8th St., New York, NY 10009 (212) 444-5678

# Если сценарию передаются несколько аргументов, #+ то выводятся только те строки, которые содержат их все.

Слишком глубокая рекурсия может привести к исчерпанию пространства, выделенного под стек, и "вываливанию" сценария по "segfault".

33.5. "Цветные" сценарии

Для установки атрибутов отображения информации на экране, таких как: жирный текст, цвет символов, цвет фона и т.п., с давних пор используются ANSI [62]

escape-последовательности. Эти последовательности широко используются в пакетных файлах DOS, эти же последовательности используются и в сценариях Bash.

Пример 33-8. "Цветная" адресная книга

#!/bin/bash # ex30a.sh: Версия сценария ex30.sh, с добавлением цвета . # Грубый пример базы данных

clear # Очистка экрана

echo -n " " echo -e '\E[37;44m'"\033[1mСписок\033[0m" # Белый текст на синем фоне echo; echo echo -e "\033[1mВыберите интересующую Вас персону:\033[0m" # Жирный шрифт tput sgr0 echo "(Введите только первую букву имени.)" echo echo -en '\E[47;34m'"\033[1mE\033[0m" # Синий tput sgr0 # сброс цвета echo "vans, Roland" # "[E]vans, Roland" echo -en '\E[47;35m'"\033[1mJ\033[0m" # Пурпурный tput sgr0 echo "ones, Mildred" echo -en '\E[47;32m'"\033[1mS\033[0m" # Зеленый tput sgr0 echo "mith, Julie" echo -en '\E[47;31m'"\033[1mZ\033[0m" # Красный tput sgr0 echo "ane, Morris" echo

read person

case "$person" in # Обратите внимание: переменная взята в кавычки.

"E" | "e" ) # Пользователь может ввести как заглавную, так и строчную букву. echo echo "Roland Evans" echo "4321 Floppy Dr." echo "Hardscrabble, CO 80753" echo "(303) 734-9874" echo "(303) 734-9892 fax" echo "revans@zzy.net" echo "Старый друг и партнер по бизнесу" ;;



"J" | "j" ) echo echo "Mildred Jones" echo "249 E. 7th St., Apt. 19" echo " New York, NY 10009" echo "(212) 533-2814" echo "(212) 533-9972 fax" echo "milliej@loisaida.com" echo "Подружка" echo "День рождения: 11 февраля" ;;

# Информация о Smith и Zane будет добавлена позднее.

* ) # Выбор по-умолчанию. # "Пустой" ввод тоже обрабатывается здесь. echo echo "Нет данных." ;;

esac

tput sgr0 # Сброс цвета

echo

exit 0

Самая простая и, на мой взгляд, самая полезная escape-последовательность -- это "жирный текст", \033[1m ... \033[0m. Здесь, комбинация \033 представляет escape-символ, кобинация "[1" -- включает вывод жирным текстом, а "[0" -- выключает. Символ "m" -- завершает каждую из escape-последовательностей.

bash$ echo -e "\033[1mЭто жирный текст.\033[0m"

Простая escape-последовательность, которая управляет атрибутом подчеркивания (в rxvt и aterm).

bash$ echo -e "\033[4mЭто подчеркнутый текст.\033[0m"

Ключ -e, в команде echo, разрешает интерпретацию escape-последовательностей.

Другие escape-последовательности, изменяющие атрибуты цвета:

bash$ echo -e '\E[34;47mЭтот текст выводится синим цветом.'; tput sgr0

bash$ echo -e '\E[33;44m'"желтый текст на синем фоне"; tput sgr0

Команда tput sgr0 возвращает настройки терминала в первоначальное состояние.

Вывод цветного текста осуществляется по следующему шаблону:.

echo -e '\E[COLOR1;COLOR2mКакой либо текст.'

Где "\E[" -- начало escape-последовательности. Числа "COLOR1" и "COLOR2", разделенные точкой с запятой, задают цвет символов и цвет фона, в соответствии с таблицей цветов, приведенной ниже. (Порядок указания цвета текста и фона не имеет значения, поскольку диапазоны числовых значений цвета для текста и фона не пересекаются). Символ "m" -- должен завершать escape-последовательность.

Обратите внимание: одиночные кавычки окружают все, что следует за echo -e.
<


Числовые значения цвета, приведенные ниже, справедливы для rxvt. Для других эмуляторов они могут несколько отличаться.

Таблица 33-1. Числовые значения цвета в escape-последовательностях

Цвет

Текст

Фон

черный 30 40
красный 31 41
зеленый 32 42
желтый 33 43
синий 34 44
пурпурный 35 45
зеленовато-голубой 36 46
белый 37 47
Пример 33-9. Вывод цветного текста

#!/bin/bash # color-echo.sh: Вывод цветных сообщений.

black='\E[30;47m' red='\E[31;47m' green='\E[32;47m' yellow='\E[33;47m' blue='\E[34;47m' magenta='\E[35;47m' cyan='\E[36;47m' white='\E[37;47m'

cecho () # Color-echo. # Аргумент $1 = текст сообщения # Аргумент $2 = цвет { local default_msg="Нет сообщений." # Не обязательно должна быть локальной.

message=${1:-$default_msg} # Текст сообщения по-умолчанию. color=${2:-$black} # Цвет по-умолчанию черный.

echo -e "$color" echo "$message" tput sgr0 # Восстановление первоначальных настроек терминала. return }

# Попробум что-нибудь вывести. # ---------------------------------------------------- cecho "Синий текст..." $blue cecho "Пурпурный текст." $magenta cecho "Позеленевший от зависти." $green cecho "Похоже на красный?" $red cecho "Циан, более известный как цвет морской волны." $cyan cecho "Цвет не задан (по-умолчанию черный)." # Аргумент $color отсутствует. cecho "\"Пустой\" цвет (по-умолчанию черный)." "" # Передан "пустой" аргумент цвета. cecho # Ни сообщение ни цвет не переданы. cecho "" "" # Функции переданы "пустые" аргументы $message и $color. # ----------------------------------------------------

echo

exit 0

# Упражнения: # --------- # 1) Добавьте в функцию 'cecho ()' возможность вывода "жирного текста". # 2) Добавьте возможность управления цветом фона.

Однако, как обычно, в бочке меда есть ложка дегтя. Escape-последовательности ANSI совершенно не переносимы. Вывод в одном эмуляторе терминала (или в консоли) может разительно отличаться от вывода в другом эмуляторе. "Расцвеченные" сценарии, дающие изумительно красивый вывод текста на одном терминале, могут давать совершенно нечитаемый текст на другом. Это ставит под сомнение практическую ценность "расцвечивания" вывода в сценариях, низводя ее до уровня никчемной "игрушки".

<


Moshe Jacobson разработал утилиту color (http://runslinux.net/projects/color), которая значительно упрощает работу с ANSI escape-последовательностями, заменяя, только что обсуждавшиеся, неуклюжие конструкции, логичным и понятным синтаксисом.

33.6. Оптимизация

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

Для начала проверьте все циклы в сценарии. Основная масса времени уходит на работу в циклах. Если это возможно, вынесите все ресурсоемкие операции за пределы циклов.

Старайтесь использовать встроенные команды. Они исполняются значительно быстрее и, как правило, не запускают подоболочку при вызове.

Избегайте использования избыточных команд, особенно это относится к конвейерам.

cat "$file" | grep "$word"

grep "$word" "$file"

# Эти команды дают один и тот же результат, #+ но вторая работает быстрее, поскольку запускает на один подпроцесс меньше.

Не следует злоупотреблять командой cat.

Для профилирования сценариев, можно воспользоваться командами time и times. Не следует пренебрегать возможностью переписать особенно критичные участки кода на языке C или даже на ассемблере.

Попробуйте минимизировать количество операций с файлами. Bash не "страдает" излишней эффективностью при работе с файлами, попробуйте применить специализированные средства для работы с файлами в сценариях, такие как awk или Perl.

Записывайте сценарии в структурированной форме, это облегчит их последующую реорганизацию и оптимизацию.


Помните, что значительная часть методов оптимизации кода, существующих в языках высокого уровня, вполне применима и к сценариям, однако есть и такие, которые не могут применяться. Основной критерий здесь -- это здравый смысл.

Прекрасный пример того, как оптимизация может сократить время работы сценария, вы найдете в Пример 12-32.

33.7. Разные советы

Для ведения учета использования сценария пользователями, добавьте следующие строки в сценарий. Они запишут в файл отчета название сценария и время запуска.

# Добавление (>>) учетной записи, об использовании сценария, в файл отчета.

date>> $SAVE_FILE # Дата и время. echo $0>> $SAVE_FILE # Название сценария. echo>> $SAVE_FILE # Пустая строка -- как разделитель записей.

# Не забудьте определить переменную окружения SAVE_FILE в ~/.bashrc # (что нибудь, типа: ~/.scripts-run)

Оператор >> производит добавление строки в конец файла. А как быть, если надо добавить строку в начало существующего файла?

file=data.txt title="***Это титульная строка в текстовом файле***"

echo $title | cat - $file >$file.new # "cat -" объединяет stdout с содержимым $file. # В результате получится #+ новый файл $file.new, в начало которого добавлена строка $title.

Само собой разумеется, то же самое можно сделать с помощью sed.

Сценарий командной оболочки может использоваться как команда внутри другого сценария командной оболочки, Tcl, или wish сценария или, даже в Makefile. Он может быть вызван как внешняя команда из программы на языке C, с помощью функции system(), т.е. system("script_name");.

Собирайте свои библиотеки часто используемых функций и определений. Эти "библиотеки" могут быть "подключены" к сценариям, с помощью команды точка (.) или source.

# Сценарий-библиотека # ------ -------

# Обратите внимание: # Здесь нет sha-bang ("#!"). # И нет "живого кода".

# Определения переменных

ROOT_UID=0 # UID root-а, 0. E_NOTROOT=101 # Ошибка -- "обычный пользователь".


MAXRETVAL=255 # Максимальное значение, которое могут возвращать функции. SUCCESS=0 FAILURE=-1

# Функции

Usage () # Сообщение "Порядок использования:". { if [ -z "$1" ] # Нет аргументов. then msg=filename else msg=$@ fi

echo "Порядок использования: `basename $0` "$msg"" }

Check_if_root () # Проверка прав пользователя. { # из примера "ex39.sh". if [ "$UID" -ne "$ROOT_UID" ] then echo "Этот сценарий должен запускаться с привилегиями root." exit $E_NOTROOT fi }

CreateTempfileName () # Создание "уникального" имени для временного файла. { # Из примера "ex51.sh". prefix=temp suffix=`eval date +%s` Tempfilename=$prefix.$suffix }

isalpha2 () # Проверка, состоит ли строка только из алфавитных символов. { # Из примера "isalpha.sh". [ $# -eq 1 ] || return $FAILURE

case $1 in *[!a-zA-Z]*|"") return $FAILURE;; *) return $SUCCESS;; esac # Спасибо S.C. }

abs () # Абсолютное значение. { # Внимание: Максимально возможное возвращаеиое значение # не может превышать 255. E_ARGERR=-999999

if [ -z "$1" ] # Проверка наличия входного аргумента. then return $E_ARGERR # Код ошибки, обычно возвращаемый в таких случаях. fi

if [ "$1" -ge 0 ] # Если не отрицательное, then # absval=$1 # оставить как есть. else # Иначе, let "absval = (( 0 - $1 ))" # изменить знак. fi

return $absval }

tolower () # Преобразование строк символов в нижний регистр {

if [ -z "$1" ] # Если нет входного аргумента, then #+ выдать сообщение об ошибке echo "(null)" return #+ и выйти из функции. fi

echo "$@" | tr A-Z a-z # Преобразовать все входные аргументы ($@).

return

# Для записи результата работы функции в переменную, используйте операцию подстановки команды. # Например: # oldvar="A seT of miXed-caSe LEtTerS" # newvar=`tolower "$oldvar"` # echo "$newvar" # a set of mixed-case letters # # Упражнение: Добавьте в эту библиотеку функцию перевода символов в верхний регистр. # toupper() [это довольно просто]. }



Для повышения ясности комментариев, выделяйте их особым образом.

## Внимание! rm -rf *.zzy ## Комбинация ключей "-rf", в команде "rm", чрезвычайно опасна, ##+ особенно при удалении по шаблону.

#+ Продолжение комментария на новой строке. # Это первая строка комментария #+ это вторая строка комментария, #+ это последняя строка комментария.

#* Обратите внимание.

#o Элемент списка.

#> Альтернативный вариант. while [ "$var1" != "end" ] #> while test "$var1" != "end"

Для создания блочных комментариев, можно использовать конструкцию if-test.

#!/bin/bash

COMMENT_BLOCK= # Если попробовать инициализировать эту переменную чем нибудь, #+ то вы получите неожиданный результат.

if [ $COMMENT_BLOCK ]; then

Блок комментария -- ================================= Это строка комментария. Это другая строка комментария. Это еще одна строка комментария. =================================

echo "Эта строка не выводится."

Этот блок комментария не вызывает сообщения об ошибке! Круто!

fi

echo "Эта строка будет выведена на stdout."

exit 0

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

С помощью служебной переменной $?, можно проверить -- является ли входной аргумент целым числом.

#!/bin/bash

SUCCESS=0 E_BADINPUT=65

test "$1" -ne 0 -o "$1" -eq 0 2>/dev/null # Проверка: "равно нулю или не равно нулю". # 2>/dev/null подавление вывода сообщений об ошибках.

if [ $? -ne "$SUCCESS" ] then echo "Порядок использования: `basename $0` целое_число" exit $E_BADINPUT fi

let "sum = $1 + 25" # Будет выдавать ошибку, если $1 не является целым числом. echo "Sum = $sum"

# Любая переменная может быть проверена таким образом, а не только входные аргументы.

exit 0

Диапазон, возвращаемых функциями значений, 0 - 255 -- серьезное ограничение.


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

Пример 33-10. Необычный способ передачи возвращаемого значения

#!/bin/bash # multiplication.sh

multiply () # Функции выполняет перемножение всех переданых аргументов. { local product=1

until [ -z "$1" ] # Пока не дошли до последнего аргумента... do let "product *= $1" shift done

echo $product # Значение не будет выведено на экран, } #+ поскольку оно будет записано в переменную.

mult1=15383; mult2=25211 val1=`multiply $mult1 $mult2` echo "$mult1 X $mult2 = $val1" # 387820813

mult1=25; mult2=5; mult3=20 val2=`multiply $mult1 $mult2 $mult3` echo "$mult1 X $mult2 X $mult3 = $val2" # 2500

mult1=188; mult2=37; mult3=25; mult4=47 val3=`multiply $mult1 $mult2 $mult3 $mult4` echo "$mult1 X $mult2 X $mult3 X mult4 = $val3" # 8173300

exit 0

Такой прием срабатывает и для строковых значений. Таким образом, функция может "возвращать" и нечисловой результат.

capitalize_ichar () # Первый символ всех строковых аргументов { #+ переводится в верхний регистр.

string0="$@" # Принять все аргументы.

firstchar=${string0:0:1} # Первый символ. string1=${string0:1} # Остаток строки.

FirstChar=`echo "$firstchar" | tr a-z A-Z` # Преобразовать в верхний регистр.

echo "$FirstChar$string1" # Выдать на stdout.

}

newstring=`capitalize_ichar "each sentence should start with a capital letter."` echo "$newstring" # Each sentence should start with a capital letter.

Используя этот прием, функция может "возвращать" даже несколько значений.

Пример 33-11. Необычный способ получения нескольких возвращаемых значений

#!/bin/bash # sum-product.sh # Функция может "возвращать" несколько значений.

sum_and_product () # Вычисляет сумму и произведение аргументов. { echo $(( $1 + $2 )) $(( $1 * $2 )) # Вывод на stdout двух значений, разделенных пробелом. }



echo echo "Первое число: " read first

echo echo "Второе число: " read second echo

retval=`sum_and_product $first $second` # Получить результат. sum=`echo "$retval" | awk '{print $1}'` # Первое значение (поле). product=`echo "$retval" | awk '{print $2}'` # Второе значение (поле).

echo "$first + $second = $sum" echo "$first * $second = $product" echo

exit 0

Следующая хитрость -- передача массива в функцию, и "возврат" массива из функции.

Передача массива в функцию выполняется посредством записи элементов массива, разделенных пробелами, в переменную, с помощью операции подстановки команды. Получить массив обратно можно, следуя вышеописанной стратегии, через вывод на stdout, а затем, с помощью все той же операции подстановки команды и оператора ( ... ) -- записать в массив.

Пример 33-12. Передача массива в функцию и возврат массива из функции

#!/bin/bash # array-function.sh: Передача массива в функцию и... # "возврат" массива из функции

Pass_Array () { local passed_array # Локальная переменная. passed_array=( `echo "$1"` ) echo "${passed_array[@]}" # Список всех элементов в новом массиве, #+ объявленном и инициализированном в функции. }

original_array=( element1 element2 element3 element4 element5 )

echo echo "original_array = ${original_array[@]}" # Список всех элементов исходного массива.

# Так можно отдать массив в функцию. # ********************************** argument=`echo ${original_array[@]}` # ********************************** # Поместив все элементы массива в переменную, #+ разделяя их пробелами. # # Обратите внимание: метод прямой передачи массива в функцию не сработает.

# Так можно получить массив из функции. # ***************************************** returned_array=( `Pass_Array "$argument"` ) # ***************************************** # Записать результат в переменную-массив.

echo "returned_array = ${returned_array[@]}"



echo "============================================================="

# А теперь попробуйте получить доступ к локальному массиву #+ за пределами функции. Pass_Array "$argument"

# Функция выведет массив, но... #+ доступ к локальному массиву, за пределами функции, окажется невозможен. echo "Результирующий массив (внутри функции) = ${passed_array[@]}" # "ПУСТОЕ" ЗНАЧЕНИЕ, поскольку это локальная переменная.

echo

exit 0

Более сложный пример передачи массивов в функции, вы найдете в Пример A-11.

Использование конструкций с двойными круглыми скобками позволяет применять C-подобный синтаксис операций присвоения и инкремента переменных, а также оформления циклов for и while. См. Пример 10-12 и Пример 10-17.

Иногда очень удобно "пропускать" данные через один и тот же фильтр, но с разными параметрами, используя конвейерную обработку. Особенно это относится к tr и grep.

# Из примера "wstrings.sh".

wlist=`strings "$1" | tr A-Z a-z | tr '[:space:]' Z | \ tr -cs '[:alpha:]' Z | tr -s '\173-\377' Z | tr Z ' '`

Пример 33-13. Игры с анаграммами

#!/bin/bash # agram.sh: Игры с анаграммами.

# Поиск анаграмм... LETTERSET=etaoinshrdlu

anagram "$LETTERSET" | # Найти все анаграммы в наборе символов... grep '.......' | # состоящие, как минимум из 7 символов, grep '^is' | # начинающиеся с 'is' grep -v 's$' | # исключая множественное число grep -v 'ed$' # и глаголы в прошедшем времени

# Здесь используется утилита "anagram" #+ которая входит в состав пакета "yawl" , разработанного автором. # http://ibiblio.org/pub/Linux/libs/yawl-0.2.tar.gz

exit 0 # Конец.

bash$ sh agram.sh islander isolate isolead isotheral

См. также Пример 27-2, Пример 12-18 и Пример A-10.

Для создания блочных комментариев можно использовать "анонимные встроенные документы". См. Пример 17-10.

Попытка вызова утилиты из сценария на машине, где эта утилита отсутствует, потенциально опасна.


Для обхода подобных проблем можно воспользоваться утилитой whatis.

CMD=command1 # Основной вариант. PlanB=command2 # Запасной вариант.

command_test=$(whatis "$CMD" | grep 'nothing appropriate') # Если 'command1' не найдена в системе, то 'whatis' вернет #+ "command1: nothing appropriate." #==> От переводчика: Будьте внимательны! Если у вас локализованная версия whatis #==> то вывод от нее может отличаться от используемого здесь ('nothing appropriate')

if [[ -z "$command_test" ]] # Проверка наличия утилиты в системе. then $CMD option1 option2 # Запуск команды с параметрами. else # Иначе, $PlanB #+ запустить command2 (запасной вариант). fi

Команда run-parts удобна для запуска нескольких сценариев, особенно в комбинации с cron или at.

Было бы неплохо снабдить сценарий графическим интерфейстом X-Window. Для этого можно порекомендовать пакеты Xscript, Xmenu и widtools. Правда, первые два, кажется больше не поддерживаются разработчиками. Зато widtools можно получить здесь.

Пакет widtools (widget tools) требует наличия библиотеки XForms. Кроме того, необходимо слегка подправить Makefile, чтобы этот пакет можно было собрать на типичной Linux-системе. Но хуже всего то, что три из шести виджетов не работают :-(( (segfault).

Для постороения приложений с графическим интерфейсом, можно попробовать Tk, или wish (надстройка над Tcl), PerlTk (Perl с поддержкой Tk), tksh (ksh с поддержкой Tk), XForms4Perl (Perl с поддержкой XForms), Gtk-Perl (Perl с поддержкой Gtk) или PyQt (Python с поддержкой Qt).