Если во время выполнения этого сценария пользователь нажмет комбинацию 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
Асинхронное выполнение
Иногда возникает необходимость решать одновременно несколько задач. Мы знаем, что все современные операционные системы, даже те, которые не являются многопользовательскими, поддерживают многозадачность. Сценарии тоже можно конструировать так, что они будут действовать в многозадачном режиме.
Обычно такие сценарии запускают один или несколько дочерних сценариев, решающих вспомогательные задачи, пока родительский сценарий продолжает выполнять основной алгоритм. Однако когда таким способом запускается целая серия сценариев, возникает проблема координации действий родителя и потомков. Например, представьте, что родитель зависит от результатов работы потомка или, наоборот, и он должен дождаться, пока другой сценарий завершится, прежде чем завершиться самому.
В bash имеется встроенная команда, помогающая управлять асинхронным выполнением в подобных ситуациях. Команда wait приостанавливает выполнение родительского сценария, пока не завершится указанный процесс (то есть дочерний сценарий).
wait
Для начала посмотрим, как действует команда wait. Для этого нам понадобятся два сценария. Ниже приводится родительский сценарий:
#!/bin/bash
# async-parent : пример асинхронного выполнения (родитель)
echo "Parent: starting..."
echo "Parent: launching child script..."
async-child &
pid=$!
echo "Parent: child (PID= $pid) launched."
echo "Parent: continuing..."
sleep 2
echo "Parent: pausing to wait for child to finish..."
wait $pid
echo "Parent: child is finished. Continuing..."
echo "Parent: parent is done. Exiting."
и дочерний сценарий:
#!/bin/bash
# async-child : пример асинхронного выполнения (потомок)
echo "Child: child is running..."
sleep 5
echo "Child: child is done. Exiting."
В этом примере дочерний сценарий тривиально прост. Фактическая работа выполняется родителем. Родительский сценарий запускает дочерний сценарий и переводит его в фоновый режим выполнения. Идентификатор дочернего процесса сохраняется в переменной pid путем присваивания ей значения параметра $!, который всегда содержит идентификатор процесса последнего задания, переведенного в фоновый режим.
Родительский сценарий продолжает работу и в конце выполняет команду wait с идентификатором процесса дочернего сценария. Это вызывает приостановку родительского сценария до завершения дочернего сценария, после чего родительский сценарий возобновляет работу и тут же завершается.
В ходе выполнения родительский и дочерний сценарии производят следующий вывод:
[[email protected] ~]$ async-parent
Parent: starting...
Parent: launching child script...
Parent: child (PID= 6741) launched.
Parent: continuing...
Child: child is running...
Parent: pausing to wait for child to finish...
Child: child is done. Exiting.
Parent: child is finished. Continuing...
Parent: parent is done. Exiting.
Именованные каналы
В большинстве Unix-подобных систем существует возможность создавать файлы специального типа, которые называются именованными каналами (named pipe). Именованные каналы создают соединения между двумя процессами и могут использоваться как обычные файлы. Они не пользуются большой популярностью, но знать о такой возможности и уметь пользоваться ею желательно.
В программировании широко известна архитектура под названием клиент/сервер, основанная на использовании механизмов взаимодействий процессов, таких как именованные каналы или сетевые соединения.
Наиболее широко архитектура клиент/сервер используется в веб-приложениях, где веб-браузеры взаимодействуют с веб-серверами. Веб-браузер действует как клиент, посылая запросы серверу, в ответ на которые сервер посылает веб-страницы.
Именованные каналы имеют некоторое сходство с файлами, но на самом деле образуют буферы, действующие по принципу очереди: первым пришел, первым вышел (First-In, First-Out, FIFO). Так же как в случае с обычными (неименованными) каналами, данные записываются с одного конца канала и извлекаются из другого. С применением именованных каналов можно, например, выполнять следующие команды:
процесс1 > именованный_канал
и
процесс2 < именованный_канал
и такая пара команд будет действовать подобно конвейеру
процесс1 | процесс2
Создание именованного канала
Прежде чем использовать именованный канал, его нужно создать. Это делается с помощью команды mkfifo:
[[email protected] ~]$ mkfifo pipe1
[[email protected] ~]$ ls -l pipe1
prw-r--r-- 1 me me 0 2012-07-17 06:41 pipe1
Здесь с помощью команды mkfifo создается именованный канал с именем pipe1. Командой ls мы исследовали созданный файл, и, как видите, первой в поле с атрибутами стоит буква p, сообщающая, что это именованный канал (pipe).
Использование именованных каналов
Чтобы показать, как работают именованные каналы, откроем два окна терминала (или, как вариант, выполним описанные ниже действия в двух виртуальных консолях). В первом терминале введите простую команду и перенаправьте ее вывод в именованный канал:
[[email protected] ~]$ ls -l > pipe1
После нажатия клавиши ENTER появится ощущение, что команда «зависла». Это объясняется тем, что с другого конца канала данные еще не были прочитаны. В таких ситуациях говорят, что канал заблокирован. Разблокировка произойдет автоматически, как только мы подключим процесс с другого конца канала и прочитаем данные из него. Во втором окне терминала введите следующую команду:
[[email protected] ~]$ cat < pipe1
Во втором терминале появится список содержимого каталога, созданный в первом окне, как результат работы команды cat. Команда ls в первом окне терминала благополучно разблокируется и завершится.
Заключительное замечание
Итак, мы закончили наше путешествие. Единственное, что осталось, — это практика, практика и еще раз практика. Даже при том, что на своем пути мы охватили широкий круг вопросов, в действительности мы лишь затронули верхушку айсберга под названием «командная строка». Существуют еще тысячи программ командной строки, которые вам предстоит открыть и изучить. Начните свои исследования с каталога /usr/bin, и вы увидите их!