Modified: $date $time
Links: $links
Attributes: $attr
EOF
done < <(ls -l | tail -n +2)
Цикл выполняет read для каждой строки в списке с содержимым каталога. Сам список создается последней строкой в сценарии. Здесь вывод подоболочки перенаправляется на стандартный ввод цикла с помощью подстановки процесса. Команда tail включена в конвейер, чтобы устранить первую строку в списке, которая не нужна.
Этот сценарий выведет примерно следующее:
[[email protected] ~]$ pro_sub | head -n 20
Filename: addresses.ldif
Size: 14540
Owner: me
Group: me
Modified: 2012-04-02 11:12
Links: 1
Attributes: -rw-r--r--
Filename: bin
Size: 4096
Owner: me
Group: me
Modified: 2012-07-10 07:31
Links: 2
Attributes: drwxr-xr-x
Filename: bookmarks.html
Size: 394213
Owner: me
Group: me
Ловушки
В главе 10 мы узнали, что программы могут реагировать на сигналы. Эту возможность можно добавить и в сценарии. Ни в одном из сценариев, написанных нами до сих пор, этого не требовалось (потому что они быстро завершаются и не создают временных файлов), но в больших и сложных сценариях процедура обработки сигналов может оказаться весьма кстати.
Проектируя большие и сложные сценарии, важно предусматривать их реакцию на неожиданный выход пользователя из системы или выключение компьютера во время их выполнения. Если возникают подобные события, всем процессам посылается сигнал. Программы, представляющие эти процессы, могут выполнять некие действия, гарантирующие корректное завершение с сохранением необходимых данных. Допустим, к примеру, что мы написали сценарий, создающий временный файл во время выполнения. При внимательном подходе к проектированию мы могли бы предусмотреть удаление этого файла по завершении сценария. Было бы неплохо также предусмотреть удаление файла в случае получения сценарием сигнала, требующего преждевременного завершения программы.
Для этой цели в bash поддерживается механизм, известный как ловушка (trap). Ловушки реализуются с применением встроенной команды с соответствующим именем trap. Команда trap имеет следующий синтаксис:
trap аргумент сигнал [сигнал...]
где аргумент — это строка, которая будет прочитана и выполнена как команда, а сигнал — идентификатор сигнала, в ответ на который будет выполнена указанная команда.
Рассмотрим простой пример:
#!/bin/bash
# trap-demo : простой пример обработки сигналов
trap "echo 'I am ignoring you.'" SIGINT SIGTERM
for i in {1..5}; do
echo "Iteration $i of 5"
sleep 5
done
Этот сценарий определяет ловушку, которая будет выполнять команду echo в ответ на сигналы SIGINT и SIGTERM, получаемые сценарием во время выполнения. Ниже показано, как выглядят попытки остановить сценарий нажатием комбинации CTRL+C:
[[email protected] ~]$ trap-demo
Iteration 1 of 5
Iteration 2 of 5
I am ignoring you.
Iteration 3 of 5
I am ignoring you.
Iteration 4 of 5
Iteration 5 of 5
Как видите, каждый раз, когда пользователь пытается прервать работу программы, она вместо этого выводит сообщение.
Иногда бывает непросто сформировать строку с требуемой последовательностью команд, поэтому на практике в качестве команды часто используют функции. Следующий пример демонстрирует применение разных функций для обработки разных сигналов:
#!/bin/bash
# trap-demo2 : простой пример обработки сигналов
exit_on_signal_SIGINT () {
echo "Script interrupted." 2>&1
exit 0
}
exit_on_signal_SIGTERM () {
echo "Script terminated." 2>&1
exit 0
}
trap exit_on_signal_SIGINT SIGINT
trap exit_on_signal_SIGTERM SIGTERM
for i in {1..5}; do
echo "Iteration $i of 5"
sleep 5
done
Этот сценарий дважды использует команду trap, настраивая ловушки для двух сигналов. В каждой ловушке используется своя функция, которая будет вызвана для обработки конкретного сигнала. Обратите внимание на включение команды exit в обе функции обработки сигналов. Без этого сценарий продолжил бы выполняться после завершения функции.
Если во время выполнения этого сценария пользователь нажмет комбинацию CTRL+C, он увидит следующее:
[[email protected] ~]$ trap-demo2
Iteration 1 of 5
Iteration 2 of 5
Script interrupted.
временные файлы
Одним из побудительных мотивов включения обработчиков сигналов в сценарии является необходимость удаления временных файлов, которые сценарии могут создавать для хранения промежуточных результатов. Выбор имен для временных файлов — целое искусство. Традиционно программы в Unix-подобных системах создают свои временные файлы в каталоге /tmp, общем для всех и предназначенном именно для таких файлов. Однако из-за того что каталог является общим, возникает проблема безопасности, особенно остро проявляющаяся в программах, действующих с привилегиями суперпользователя. Помимо очевидной необходимости установки соответствующих разрешений для файлов, которые могут быть доступны всем пользователям в системе, важно также давать временным файлам непредсказуемые имена. Это поможет избежать атак вида гонка за временными файлами (temp race attack). Ниже показан один из способов создания непредсказуемого (но все еще осмысленного) имени:
tempfile=/tmp/$(basename $0).$$.$RANDOM
Эта команда сконструирует имя файла из имени программы, идентификатора процесса (PID) и случайного целого числа. Но имейте в виду, что переменная командной оболочки $RANDOM возвращает значения только из диапазона от 1 до 32 767, не очень большого по компьютерным меркам, поэтому единственного экземпляра переменной недостаточно, чтобы противостоять заинтересованному злоумышленнику.
Лучший результат дает программа mktemp (не путайте с функцией mktemp из стандартной библиотеки) — она автоматически выбирает имя и создает временный файл. Программа mktemp принимает аргумент с шаблоном, на основе которого конструирует имя файла. Шаблон должен включать последовательность символов X, которые будут заменены соответствующим числом случайных букв и цифр. Чем длиннее последовательность из символов X, тем длиннее последовательность случайных символов. Например:
tempfile=$(mktemp /tmp/foobar.$$.XXXXXXXXXX)
Эта команда создаст временный файл и сохранит его имя в переменной tempfile. Символы X в шаблоне будут заменены случайными буквами и цифрами, соответственно окончательное имя файла (которое в данном примере включает также значение специального параметра $$, возвращающего идентификатор процесса) может выглядеть, например, так:
/tmp/foobar.6593.UOZuvM6654
Несмотря на то что страница справочного руководства (man) для mktemp указывает, что mktemp создает имя временного файла, она также создает сам файл.
В сценариях, предназначенных для запуска рядовыми пользователями, разумнее отказаться от использования каталога /tmp и создать каталог для временных файлов в домашнем каталоге пользователя: например, так:
[[ -d $HOME/tmp ]] || mkdir $HOME/tmp
Асинхронное выполнение
Иногда возникает необходимость решать одновременно несколько задач. Мы знаем, что все современные операционные системы, даже те, которые не являются многопользовательскими, поддерживают многозадачность. Сценарии тоже можно конструировать так, что они будут действовать в многозадачном режиме.
Обычно такие сценарии запускают один или несколько дочерних сценариев, решающих вспомогательные задачи, пока родительский сценарий продолжает выполнять основной алгоритм. Однако когда таким способом запускается целая серия сценариев, возникает проблема координации действий родителя и потомков. Например, представьте, что родитель зависит от результатов работы потомка или, наоборот, и он должен дождаться, пока другой сценарий завершится, прежде чем завершиться самому.