Приложение E. Подробное введение в операции ввода-вывода и перенаправление ввода-вывода

написано Stephane Chazelas и дополнено автором документа

Практически любая команда предполагает доступность 3-х файловых дескрипторов. Первый -- 0 (стандвртный ввод, stdin), доступный для чтения. И два других -- 1 (stdout) и 2 (stderr), доступные для записи.

Запись, типа ls 2>&1, означает временное перенаправление вывода, с устройства stderr на устройство stdout.

В соответствии с соглашениями, команды принимают ввод из файла с дескриптором 0 (stdin), выводят результат работы в файл с дескриптором 1 (stdout), а сообщения об ошибках -- в файл с дескриптором 2 (stderr). Если какой либо из этих трех дескрипторов окажется закрытым, то могут возникнуть определенные проблемы:

bash$ cat /etc/passwd >&-
cat: standard output: Bad file descriptor

К примеру, когда пользователь запускает xterm, то он сначала выполняет процедуру инициализации, а затем, перед запуском командной оболочки, xterm трижды открывает терминальные устройства (/dev/pts/<n>, или нечто подобное).

После этого, командная оболочка наследует эти три дескриптора, и любая команда, запускаемая в этой оболочке, так же наследует их. Термин перенаправление -- означает переназначение одного файлового дескриптора на другой (канал (конвейер) или что-то другое). Переназначение может быть выполнено локально (для отдельной команды, для группы команд, для подоболочки, для операторов while, if, case, for...) или глобально (с помощью exec).

ls > /dev/null -- означает запуск команды ls с файловым дескриптором 1, присоединенным к устройству /dev/null.

bash$ lsof -a -p $$ -d0,1,2
COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME
 bash 363 bozo 0u CHR 136,1 3 /dev/pts/1
 bash 363 bozo 1u CHR 136,1 3 /dev/pts/1
 bash 363 bozo 2u CHR 136,1 3 /dev/pts/1


bash$ exec 2> /dev/null
bash$ lsof -a -p $$ -d0,1,2
COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME
 bash 371 bozo 0u CHR 136,1 3 /dev/pts/1
 bash 371 bozo 1u CHR 136,1 3 /dev/pts/1
 bash 371 bozo 2w CHR 1,3 120 /dev/null


bash$ bash -c 'lsof -a -p $$ -d0,1,2' | cat
COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME
 lsof 379 root 0u CHR 136,1 3 /dev/pts/1
 lsof 379 root 1w FIFO 0,0 7118 pipe
 lsof 379 root 2u CHR 136,1 3 /dev/pts/1


bash$ echo "$(bash -c 'lsof -a -p $$ -d0,1,2' 2>&1)"
COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME
 lsof 426 root 0u CHR 136,1 3 /dev/pts/1
 lsof 426 root 1w FIFO 0,0 7520 pipe
 lsof 426 root 2w FIFO 0,0 7520 pipe

Упражнение: Проанализируйте следующий сценарий.

#! /usr/bin/env bash

mkfifo /tmp/fifo1 /tmp/fifo2
while read a; do echo "FIFO1: $a"; done  /tmp/fifo1
exec 8> >(while read a; do echo "FD8: $a, to fd7"; done >&7)

exec 3>&1
(
 (
 (
 while read a; do echo "FIFO2: $a"; done &7 &
 exec 3> /tmp/fifo2

 echo 1st, to stdout
 sleep 1
 echo 2nd, to stderr >&2
 sleep 1
 echo 3rd, to fd 3 >&3
 sleep 1
 echo 4th, to fd 4 >&4
 sleep 1
 echo 5th, to fd 5 >&5
 sleep 1
 echo 6th, through a pipe | sed 's/.*/PIPE: &, to fd 5/' >&5
 sleep 1
 echo 7th, to fd 6 >&6
 sleep 1
 echo 8th, to fd 7 >&7
 sleep 1
 echo 9th, to fd 8 >&8

 ) 4>&1 >&3 3>&- | while read a; do echo "FD4: $a"; done 1>&3 5>&- 6>&-
 ) 5>&1 >&3 | while read a; do echo "FD5: $a"; done 1>&3 6>&-
) 6>&1 >&3 | while read a; do echo "FD6: $a"; done 3>&-

rm -f /tmp/fifo1 /tmp/fifo2


# Выясните, куда переназначены файловые дескрипторы каждой команды и подоболочки.

exit 0