Документация по ОС FreeBSD Пятница, 19.04.2024, 08:28
Приветствую Вас Гость | RSS
Меню сайта

Категории каталога
Shell [40]

Главная » Статьи » Программирование » Shell

Перенаправления в bash при помощи exec [2010]
Если вы частенько работаете в командной строке, вы должны быть знакомы с перенаправлением ввода/вывода. Но, возможно, вы не знакомы с перенаправлением ввода/вывода внутри bash-скрипта. Я не имею ввиду перенаправление, используемое при вызове других программ из скрипта, я говорю о перенаправлении ввода/вывода вашего скрипта в целом, начиная с момента его запуска.
Давайте, например, предположим, что вам понадобилось снабдить ваш скрипт опцией «--log», при помощи которой пользователь мог бы перенаправлять весь вывод работы вашего скрипта в нужный лог-файл. Конечно, пользователь мог бы просто перенаправить вывод скрипта средствами bash, но давайте представим, что по каким-то причинам такое решение не годится. Давайте реализуем это:

#!/bin/bash
 
echo hello
 
# Разбираем опции командной строки.
# Выполняем следующий код, если нашли опцию --log
if test -t 1; then
    # Stdout привязан к терминалу
    exec >log
else
    # Stdout не привязан к терминалу, 
    # протокол вести не получится
    false
fi
 
echo goodbye
echo error >&2

В первом утверждении if при помощи test выполняется проверка, подключён ли файловый дескриптор с номером один (поток стандартного вывода) к терминалу. Если это так, тоexec «переоткрывает» его для записи в файл log. Вызов exec в контексте текущей оболочки без передачи ей команды, а только с указанием перенаправления вывода, приводит к тому, что вы можете открывать/закрывать файлы, дублируя их дескрипторы. Если же файловый дескриптор с номером 1 не подключён к терминалу, то мы просто не делаем ничего.

Если вы запустите вышеприведённый скрипт, то вы увидите, что первая и последняя echoвыполнят свой вывод в терминал. Первая echo сработает потому, что она появляется до включения перенаправления, а вторая — поскольку в сценарии её вывод перенаправлен в стандартный поток ошибок (файловый дескриптор номер 2). И как же перенаправить стандартный поток ошибок в тот же файл? Всего ли одно небольшое изменение в вызовеexec:

#!/bin/bash
 
echo hello
 
if test -t 1; then
    # Stdout привязан к терминалу
    exec >log 2>&1
else
    # Stdout не привязан к терминалу, 
    # протокол вести не получится
    false
fi
 
echo goodbye
echo error >&2

В примере выше exec перенаправляет поток ошибок туда же, куда направлен поток вывода (это, собственно, и называется дублированием файловых дескрипторов). Имейте ввиду, что порядок здесь очень важен: если вы его измените и переоткроете сперва поток ошибок (т. е. exec 2>&1 >log), то весь вывод всё равно останется направленным на терминал, поскольку он будет направлен туда же, куда и поток вывода, а тот, в свою очередь по умолчанию направлен в терминал.

Ради интереса можно попробовать выполнить то же самое, когда поток стандартного вывода не подключён к терминалу. Мы не сможем этого сделать, если поток вывода уже подключён к другому файлу или перенаправлен в конвейер, поскольку таким образом мы нарушим существовавшее перенаправление.

Возьмём, например, команду:

bash test.sh | grep good

То, что нам нужно в конечном итоге, это эквивалент следующей команды:

bash test.sh | tee log | grep good

Вероятно, вашей первой мыслью будет вызвать exec как-то так:

exec | tee log &    # Работать не будет

пытаясь таким образом при помощи exec перенаправить вывод в tee, запущенной в фоновом режиме. Но это не будет работать (хотя bash ничего и не скажет). Такая конструкция всего лишь перенаправляет вывод exec в tee, а поскольку exec не выводит ничего, то tee создаст пустой файл и на этом закончит.

Есть ещё одна мысль: запустить tee в фоне, направив в неё ввод из одного файлового дескриптора, а вывод перенапровить в другой. И вы можете это сделать, но проблема в том, что не существует способа создать новый процесс, стандартный ввод которого был бы подключён к каналу. Если бы мы могли это сделать, то получить вывод tee было бы просто, поскольку по умолчанию он направлен туда же, куда и вывод сценария целиком. Таким образом, мы могли бы просто закрыть поток вывода сценария и подключить его к нашему каналу, если бы была возможность этот канал создать.

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

#!/bin/bash
 
echo hello
 
if test -t 1; then
    # Stdout is a terminal.
    exec >log
else
    # Stdout is not a terminal.
    npipe=/tmp/$$.tmp
    trap "rm -f $npipe" EXIT
    mknod $npipe p
    tee <$npipe log &
    exec 1>&-
    exec 1>$npipe
fi
 
echo goodbye

Здесь, если поток стандартного вывода не подключён к терминалу, мы создаём именованный канал (канал, который располагается в виде файла в файловой системе) при помощи mknod, и при помощи trap удаляем его после завершения работы сценария. Затем мы запускаем tee, связывая его поток ввода с созданным каналом и указываем выполнять запись в файл log. Помните, что tee кроме записи в файл всего полученного из потока ввода, также выводит всё в поток стандартного вывода. Также, вспомните о том, что поток вывода tee направлен туда же, куда и весь вывод сценария, вызывающего tee. Таким образом, весь вывод tee будет попадать туда, куда в данный момент направлен поток вывода нашего сценария, то есть, в перенаправленный пользователем поток вывода или канал конвейера, заданные в момент вызова сценария из командной строки. Так что теперь мы получили стандартный вывод tee там, где нам это нужно: в перенаправление или канал конвейера, определённый пользователем.

Теперь остаётся передать на вход tee нужные данные. Поскольку tee теперь считывает данные из именованного канала, всё что нам необходимо, это перенаправить стандартный вывод в именованный канал. Мы закрываем текущий поток вывода (exec 1>&- ) и открываем его в именованный канал (exec 1>$npipe). Обратите внимание, что закрытие текущего потока вывода ничего не нарушает, поскольку tee также осуществляет вывод в перенаправленный пользователем поток или канал.

Теперь, когда вы выполните команду и направите её вывод в канал к grep, вы получите и стандартный вывод и запись в лог-файле.

Подобных приёмов масса, откройте man-страницу bash!

P.S. В Bash 4 то же самое можно сделать при помощи конструкции coproc, но об этом в другой раз.


Источник: http://www.ashep.org/2010/perenapravleniya-v-bash-pri-pomoshhi-exec/
Категория: Shell | Добавил: oleg (25.09.2010) | Автор: ashep
Просмотров: 1367 | Рейтинг: 0.0/0 |
Всего комментариев: 0
Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]
Форма входа

Beastie

Друзья сайта

Статистика

Онлайн всего: 1
Гостей: 1
Пользователей: 0
links

Copyright MyCorp © 2024