Мендель Купер - Искусство программирования на языке сценариев командной оболочки
echo "Вывод команды "ls -al""
echo
ls -al
echo; echo
echo "Вывод команды "df""
echo
df
# ----------------------------------------------------------- #
exec 1>&6 6>&- # Восстановить stdout и закрыть дескр. #6.
echo
echo "== stdout восстановлено в значение по-умолчанию == "
echo
ls -al
echo
exit 0
Пример 16-3. Одновременное перенаправление устройств, stdin и stdout, с помощью команды exec
#!/bin/bash
# upperconv.sh
# Преобразование символов во входном файле в верхний регистр.
E_FILE_ACCESS=70
E_WRONG_ARGS=71
if [ ! -r "$1" ] # Файл доступен для чтения?
then
echo "Невозможно прочитать из заданного файла!"
echo "Порядок использования: $0 input-file output-file"
exit $E_FILE_ACCESS
fi # В случае, если входной файл ($1) не задан
#+ код завершения будет этим же.
if [ -z "$2" ]
then
echo "Необходимо задать выходной файл."
echo "Порядок использования: $0 input-file output-file"
exit $E_WRONG_ARGS
fi
exec 4<&0
exec < $1 # Назначить ввод из входного файла.
exec 7>&1
exec > $2 # Назначить вывод в выходной файл.
# Предполагается, что выходной файл доступен для записи
# (добавить проверку?).
# -----------------------------------------------
cat - | tr a-z A-Z # Перевод в верхний регистр
# ^^^^^ # Чтение со stdin.
# ^^^^^^^^^^ # Запись в stdout.
# Однако, и stdin и stdout были перенаправлены.
# -----------------------------------------------
exec 1>&7 7>&- # Восстановить stdout.
exec 0<&4 4<&- # Восстановить stdin.
# После восстановления, следующая строка выводится на stdout, чего и следовало ожидать.
echo "Символы из "$1" преобразованы в верхний регистр, результат записан в "$2"."
exit 0
16.2. Перенаправление для блоков кода
Блоки кода, такие как циклы while, until и for, условный оператор if/then, так же могут смешиваться с перенаправлением stdin. Даже функции могут использовать эту форму перенаправления (см. Пример 22-7). Оператор перенаправления <, в таких случаях, ставится в конце блока.
Пример 16-4. Перенаправление в цикл while
#!/bin/bash
if [ -z "$1" ]
then
Filename=names.data # По-умолчанию, если имя файла не задано.
else
Filename=$1
fi
# Конструкцию проверки выше, можно заменить следующей строкой (подстановка параметров):
#+ Filename=${1:-names.data}
count=0
echo
while [ "$name" != Smith ] # Почему переменная $name взята в кавычки?
do
read name # Чтение из $Filename, не со stdin.
echo $name
let "count += 1"
done <"$Filename" # Перенаправление на ввод из файла $Filename.
# ^^^^^^^^^^^^
echo; echo "Имен прочитано: $count"; echo
# Обратите внимание: в некоторых старых командных интерпретаторах,
#+ перенаправление в циклы приводит к запуску цикла в субоболочке (subshell).
# Таким образом, переменная $count, по окончании цикла, будет содержать 0,
# значение, записанное в нее до входа в цикл.
# Bash и ksh стремятся избежать запуска субоболочки (subshell), если это возможно,
#+ так что этот сценарий, в этих оболочках, работает корректно.
#
# Спасибо Heiner Steven за это примечание.
exit 0
Пример 16-5. Альтернативная форма перенаправления в цикле while
#!/bin/bash
# Это альтернативный вариант предыдущего сценария.
# Предложил: by Heiner Steven
#+ для случаев, когда циклы с перенаправлением
#+ запускаются в субоболочке, из-за чего переменные, устанавливаемые в цикле,
#+ не сохраняют свои значения по завершении цикла.
if [ -z "$1" ]
then
Filename=names.data # По-умолчанию, если имя файла не задано.
else
Filename=$1
fi
exec 3<&0 # Сохранить stdin в дескр. 3.
exec 0<"$Filename" # Перенаправить stdin.
count=0
echo
while [ "$name" != Smith ]
do
read name # Прочитать с перенаправленного stdin ($Filename).
echo $name
let "count += 1"
done <"$Filename" # Цикл читает из файла $Filename.
# ^^^^^^^^^^^^
exec 0<&3 # Восстановить stdin.
exec 3<&- # Закрыть временный дескриптор 3.
echo; echo "Имен прочитано: $count"; echo
exit 0
Пример 16-6. Перенаправление в цикл until
#!/bin/bash
# То же самое, что и в предыдущем примере, только для цикла "until".
if [ -z "$1" ]
then
Filename=names.data # По-умолчанию, если файл не задан.
else
Filename=$1
fi
# while [ "$name" != Smith ]
until [ "$name" = Smith ] # Проверка != изменена на =.
do
read name # Чтение из $Filename, не со stdin.
echo $name
done <"$Filename" # Перенаправление на ввод из файла $Filename.
# ^^^^^^^^^^^^
# Результаты получаются теми же, что и в случае с циклом "while", в предыдущем примере.
exit 0
Пример 16-7. Перенаправление в цикл for
#!/bin/bash
if [ -z "$1" ]
then
Filename=names.data # По-умолчанию, если файл не задан.
else
Filename=$1
fi
line_count=`wc $Filename | awk '{ print $1 }'`
# Число строк в файле.
#
# Слишком запутано, тем не менее показывает
#+ возможность перенаправления stdin внутри цикла "for"...
#+ если вы достаточно умны.
#
# Более короткий вариант line_count=$(wc < "$Filename")
for name in `seq $line_count` # "seq" выводит последовательность чисел.
# while [ "$name" != Smith ] -- более запутанно, чем в случае с циклом "while" --
do
read name # Чтение из файла $Filename, не со stdin.
echo $name
if [ "$name" = Smith ]
then
break
fi
done <"$Filename" # Перенаправление на ввод из файла $Filename.
# ^^^^^^^^^^^^
exit 0
Предыдущий пример можно модифицировать так, чтобы перенаправить вывод из цикла.
Пример 16-8. Перенаправление устройств (stdin и stdout) в цикле for
#!/bin/bash
if [ -z "$1" ]
then
Filename=names.data # По-умолчанию, если файл не задан.
else
Filename=$1
fi
Savefile=$Filename.new # Имя файла, в котором сохраняются результаты.
FinalName=Jonah # Имя, на котором завершается чтение.
line_count=`wc $Filename | awk '{ print $1 }'` # Число строк в заданном файле.
for name in `seq $line_count`
do
read name
echo "$name"
if [ "$name" = "$FinalName" ]
then
break
fi
done < "$Filename" > "$Savefile" # Перенаправление на ввод из файла $Filename,
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^ и сохранение результатов в файле.
exit 0
Пример 16-9. Перенаправление в конструкции if/then
#!/bin/bash
if [ -z "$1" ]
then
Filename=names.data # По-умолчанию, если файл не задан.
else
Filename=$1
fi
TRUE=1
if [ "$TRUE" ] # конструкции "if true" и "if :" тоже вполне допустимы.
then
read name
echo $name
fi <"$Filename"
# ^^^^^^^^^^^^
# Читает только первую строку из файла.
exit 0
Пример 16-10. Файл с именами "names.data", для примеров выше
Aristotle
Belisarius
Capablanca
Euler
Goethe
Hamurabi
Jonah
Laplace
Maroczy
Purcell
Schmidt
Semmelweiss
Smith
Turing
Venn
Wilson
Znosko-Borowski
# Это файл с именами для примеров
#+ "redir2.sh", "redir3.sh", "redir4.sh", "redir4a.sh", "redir5.sh".
Перенаправление stdout для блока кода, может использоваться для сохранения результатов работы этого блока в файл. См. Пример 3-2.
Встроенный документ -- это особая форма перенаправления для блоков кода.
16.3. Область применения
Как один из вариантов грамотного применения перенаправления ввода/вывода, можно назвать разбор и "сшивание" вывода от команд (см. Пример 11-6). Это позволяет создавать файлы отчетов и журналов регистрации событий.
Пример 16-11. Регистрация событий
#!/bin/bash
# logevents.sh, автор: Stephane Chazelas.
# Регистрация событий в файле.
# Сценарий должен запускаться с привилегиями root (что бы иметь право на запись в /var/log).
ROOT_UID=0 # Привилегии root имеет только пользователь с $UID = 0.
E_NOTROOT=67 # Код завершения, если не root.
if [ "$UID" -ne "$ROOT_UID" ]
then
echo "Сценарий должен запускаться с привилегиями root."
exit $E_NOTROOT
fi
FD_DEBUG1=3
FD_DEBUG2=4
FD_DEBUG3=5
# Раскомментарьте одну из двух строк, ниже, для активизации сценария.
# LOG_EVENTS=1
# LOG_VARS=1
log() # Запись даты и времени в файл.
{
echo "$(date) $*" >&7 # Добавляет в конец файла.
# См. ниже.
}
case $LOG_LEVEL in
1) exec 3>&2 4> /dev/null 5> /dev/null;;
2) exec 3>&2 4>&2 5> /dev/null;;
3) exec 3>&2 4>&2 5>&2;;
*) exec 3> /dev/null 4> /dev/null 5> /dev/null;;
esac
FD_LOGVARS=6
if [[ $LOG_VARS ]]
then exec 6>> /var/log/vars.log
else exec 6> /dev/null # Подавить вывод.
fi
FD_LOGEVENTS=7
if [[ $LOG_EVENTS ]]
then
# then exec 7 >(exec gawk '{print strftime(), $0}' >> /var/log/event.log)
# Строка, выше, не работает в Bash, версии 2.04.
exec 7>> /var/log/event.log # Добавление в конец "event.log".
log # Записать дату и время.