附录 F. I/O 和 I/O 重定向的详细介绍

由 St�phane Chazelas 撰写,并由文档作者修订

一个命令预期前三个 文件描述符 可用。第一个,fd 0 (标准输入,stdin),用于读取。另外两个 (fd 1stdoutfd 2stderr) 用于写入。

每个命令都有一个stdin, stdout,以及一个stderr与之关联。ls 2>&1意味着临时连接 ls 命令的stderr到与 shell 的stdout.

相同的 "资源"。按照惯例,一个命令从 fd 0 (stdin) 读取其输入,将正常输出打印到 fd 1 (stdout),并将错误输出打印到 fd 2 (stderr)。如果这三个 fd 之一未打开,您可能会遇到问题。

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

例如,当 xterm 运行时,它首先初始化自身。在运行用户的 shell 之前,xterm 打开终端设备 (/dev/pts/<n> 或类似设备) 三次。

此时,Bash 继承了这三个文件描述符,并且 Bash 运行的每个命令(子进程)依次继承它们,除非您重定向命令。重定向 意味着将文件描述符之一重新分配给另一个文件(或管道,或任何允许的内容)。文件描述符可以在本地重新分配(对于命令、命令组、子 shellwhile 或 if 或 case 或 for 循环...),或全局重新分配,用于 shell 的其余部分(使用 exec)。

ls > /dev/null意味着运行 ls,其 fd 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 7> /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 < /tmp/fifo2 | tee /dev/stderr \
   | tee /dev/fd/4 | tee /dev/fd/5 | tee /dev/fd/6 >&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


# For each command and subshell, figure out which fd points to what.
# Good luck!

exit 0