Описание: Кандидат BSDA должен уметь перенаправлять стандартный вывод, ввод или поток ошибок программы, использовать pipe чтобы послать вывод одной программы в другую программу или в файл. Использовать tee(1) чтобы копировать стандартный ввод на стандартный вывод.
Практика: <
, >
,
|
, tee(1), >&
и |&
За каждой программой запущенной в UNIX (и не
только UNIX) закреплено минимум три файловых
дескриптора: стандартный ввод (STDIN
),
стандартный вывод (STDOUT
) и стандартный
вывод ошибок (STDERR
). Хотя мы говорим
«файловый дескриптор», на самом деле это не
обязательно именно файлы. Речь идёт об «обобщённых
файлах» — некоторых объектах, куда можно писать
и откуда можно читать. В норме, приложение запущенное в
терминале направляет STDOUT
и
STDERR
на консоль. Таким образом, мы видим
результат деятельности программы напечатанным на экране.
STDIN
программы, это поток информации
читаемый ею с клавиатуры.
Напрмер, запустим программу grep следующим образом:
$
grep r
В этом случае программа grep будет искать строки содержащие
букву r в потоке STDIN
(то есть в тексте
набираемом с клавиатуры), и выводить этот текст на
STDOUT
(то есть на экран). Сразу после
запуска ничего не происходит: команда grep ожидает ввода с
клавиатуры т.е. читает STDIN
. Мы печатаем
текст и нажимаем клавишу <Enter>. После этого строка
попадает в grep и если она содержит букву r она печатается
второй раз на экране т.е. grep печатает её на
STDOUT
.
Ниже приведён листинг такого примера. Знаком < помечены
строки котороые набрал пользователь (STDIN
),
а знаком > строки напечатанные в ответ программой
grep(1).
$
grep r
< DragonFly BSD
> DragonFly BSD
< FreeBSD
> FreeBSD
< OpenBSD
< NetBSD
В нашем листинге STDIN
и
STDOUT
обозначены значками < и >. В
жизни этого, конечно, не происходит. Всё вперемешку, что крайне
неудобно. Да и вообще, трудно представить себе что кто-то будет
руками набивать текст только для того, чтобы его профильтровал
grep(1). Гораздо удобнее пользоваться
символами перенаправления для переопределения стандартного ввода
и вывода.
Пусть у нас есть файл BSDA
содержащий
названия изучаемых нами BSD систем.
$
cat BSDA
DragonFly BSD
FreeBSD
OpenBSD
NetBSD
Применим grep для того, чтобы выяснить какие системы BSD
содержат в своём названии букву r. Для этого мы переопределим
STDIN
команды grep. Теперь вместо того, чтобы
читать текст с клавиатуры, grep(1) будет
читать его из файла BSDA
:
$
grep r < BSDA
DragonFly BSD
FreeBSD
А что если нам надо сохранить вывод программы grep в файл? Тогда
мы должны переопределить ещё и STDOUT
:
$
grep r < BSDA > BSDA-r
Теперь у нас появился файл с названием
BSDA-r
содержащий две строки:
$
cat BSDA-r
DragonFly BSD
FreeBSD
Но а что если нам надо узнать сколько систем BSD имеют в своём
имени букву r? Для этого мы можем воспользоваться программой
wc(1) с аргументом -l
.
Команда wc -l
считает
строки в своём STDIN
и печатает результат на
STDOUT
. Таким образом, мы можем выполнить
последовательно 2 команды:
$
grep r < BSDA > BSDA-r$
wc -l < BSDA-r 2
Но это неудобно: мы зачем-то создавали временный файл,
передавали копеечную информацию и для этого обращались к диску,
а это медленная операция. А если бы у нас было много информации,
то наши действия тоже были бы нерациональны: Весь
STDOUT
grep(1)'а мог не
поместиться на диске (может там миллион строк!), но он и не
нужен wc(1) для работы,
wc(1) может каждый отдельный момент работать
с кусочком файла.
Напрашивается естественный вывод: надо переопределить
STDOUT
grep'а так, чтобы он стал
STDIN
'ом wc(1). Такую
конструкцию называют pipe или конвейер.
$
grep r < BSDA | wc -l
2
Стандартный вывод wc(1) (число 2) тоже можно передать на стандартный ввод другой программы, таким образом длину конвейера или трубы (pile-line) можно сделать сколь угодно длинной. В следующем примере количество систем BSD содержащих в своём названии букву r будет распечатано на принтере:
$
grep r < BSDA | wc -l | lpr
А чтоже делать, если мы хотим и список систем получить и
посчитать их количество? Можно как и прежде выполнить 2 действия
поочереди: сперва создать файл BSDA-r
,
полюбоваться на него, а потом скормить его программе wc. А можно
воспользоваться программой tee(1). tee ничего
не делает с потоком данных, которые через неё идут, она просто
копирует STDIN
в STDOUT
.
Но если ей в качестве аргумента задать некоторый файл, то она
будет заодно записывать эту информацию и в него. Таких файлов
команде tee можно задать много. Таким образом, tee является
разветвителем в трубе:
$
grep r < BSDA | tee BSDA-r | wc -l 2$
cat BSDA-r DragonFly BSD FreeBSD
tee можно использовать как здесь — для сохранения
промежуточных результатов, а можно использовать для того, чтобы
протоколировать в файл то, что администратор видит на экране.
Напрмер, ниже программа make будет писать что-то на экран, но
впоследствии мы сможем прочитать что она там писала из файла
make-log
:
$
make | tee make-log
В этом примере мы добились развоения стандартного вывода программы make: этот поток информации одновременно пишется в файл make-log и печатается в окне терминала обычным образом.
Теперь поговорим про STDERR
. Пусть у нас в
текущем каталоге есть два файла get.sh
и
put.sh
, и более ничего нет. Выполним
следующее действие:
$
ls *sh *gz
ls: *gz: No such file or directory
get.sh put.sh
Мы видим, что на экране присуствует как список файлов с
расширением sh так и сообщение об ошибке связанное с тем, что в
текущем каталоге отсутствуют файлы с расширением gz. Обе эти
строки напечатаны в окне терминала, но это два разных потока.
Сообщение об ошибке было напечатано в стандартный вывод
ошибок — STDERR
. Убедимся в этом:
$
ls *sh *gz > /dev/null ls: *gz: No such file or directory$
ls *sh *gz 2> /dev/null get.sh put.sh
В приведённом примере мы в первом случае перенаправили
STDOUT
в файл /dev/null (это уcтройство
поглащающее байты вроде «чёрной дыры»), а во втором
случае мы перенаправили STDERR
. Поэтому в
первом случае у нас напечаталось только сообщение об ошибке, а
во втором только список файлов с расширением sh.
Файловые дескрипторы соответствующие STDIN
,
STDOUT
и STDERR
имеют
номера, соответственно 0, 1 и 2. Когда мы пишем знак >
, система неявно предполагает, что мы
перенаправляем дескриптор с номером 1 и перенаправляет
STDOUT
. Если же мы хотим перенаправить
STDERR
нам надо явно указать его номер: 2>
.
Важно | |
---|---|
2> пишется слитно без пробела.
|
Вы можете объединить файловые дескрипторы, если вам надо,
например, писать сообщения выводящиеся на
STDOUT
и STDERR
в один
файл:
$
make > make.log 2>&1
Здесь мы направили стандартный вывод в файл
make.log
(написав > make.log
), а затем
STDERR
перенаправили в
STDOUT
(написав 2>&1
). Причём порядок действий
здесь важен. Команда
$
make 2>&1 > make.log
приведёт к тому, что в файл make.log
будет
направлен только STDOUT
, так как
STDERR
был перенаправлен тогда, когда
STDOUT
направлялся ещё на консоль.
Синтаксис перенаправления в csh(1) отличается от синтакиса в sh(1). Ниже приведены эквивалентные команды на sh(1) и на csh(1):
sh:$
make 2>&1 > make.log csh:%
make >& make.log sh:$
make 2>&1 | less csh:%
make |& less sh:$
make 2>/dev/null > make.log csh:%
(make > make.log) >& /dev/null
В последней строке продемонстрировано досадное ограничение
csh(1): для того, чтобы перенаправить
STDERR
и STDOUT
в разные
места приходится заворачиваться в блин: перенаправить
произвольный файловый дескриптор по его номеру, увы, нельзя.
Можно лишь пользоваться перенаправлением суммы
STDERR
и STDOUT
используя
символы |&
и >&
.