Описание: Кандидат 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 используя
символы |& и >&.
