Appendix E. I/O和I/O重定向的详细介绍

由Stephane Chazelas编写, 本书作者修订

一个命令期望前3个文件描述符是可用的. 第一个, fd 0(标准输入, stdin), 用作读取. 另外两个, (fd 1, stdoutfd 2, stderr), 用来写入.

每个命令都会关联到stdin, stdout, 和stderr. ls 2>&1意味着临时的将ls命令的stderr连接到shell的stdout.

按惯例, 命令一般都是从fd 0(stdin)上读取输入, 打印输出到fd 1(stdout)上, 错误输出一般都输出到fd 2(stderr)上. 如果这3个文件描述中的某一个没打开, 你可能就会遇到麻烦了:

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

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

这里, Bash继承了这三个文件描述符, 而且每个运行在Bash上的命令(子进程)也都依次继承了它们, 除非你重定向了这些命令. 重定向意味着将这些文件描述符中的某一个, 重新分配到其他文件中(或者分配到一个管道中, 或者是其他任何可能的东西). 文件描述符既可以被局部重分配(对于一个命令, 命令组, 一个子shell, 一个while循环, if或case结构...), 也可以全局重分配, 对于余下的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

这是用来展示不同类型的重定向.

练习: 分析下面的脚本.
  1 #! /usr/bin/env bash                                                                                    
  2 												
  3 mkfifo /tmp/fifo1 /tmp/fifo2                                                                            
  4 while read a; do echo "FIFO1: $a"; done < /tmp/fifo1 &                                                  
  5 exec 7> /tmp/fifo1                                                                                      
  6 exec 8> >(while read a; do echo "FD8: $a, to fd7"; done >&7)                                            
  7                                                                                                         
  8 exec 3>&1                                                                                               
  9 (                                                                                                       
 10  (                                                                                                      
 11   (                                                                                                     
 12    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 &                                                                        
 13    exec 3> /tmp/fifo2                                                                                   
 14                                                                                                         
 15    echo 1st, to stdout                                                                                  
 16    sleep 1                                                                                              
 17    echo 2nd, to stderr >&2                                                                              
 18    sleep 1                                                                                              
 19    echo 3rd, to fd 3 >&3                                                                                
 20    sleep 1                                                                                              
 21    echo 4th, to fd 4 >&4                                                                                
 22    sleep 1                                                                                              
 23    echo 5th, to fd 5 >&5                                                                                
 24    sleep 1                                                                                              
 25    echo 6th, through a pipe | sed 's/.*/PIPE: &, to fd 5/' >&5                                          
 26    sleep 1                                                                                              
 27    echo 7th, to fd 6 >&6                                                                                
 28    sleep 1                                                                                              
 29    echo 8th, to fd 7 >&7                                                                                
 30    sleep 1                                                                                              
 31    echo 9th, to fd 8 >&8                                                                                
 32                                                                                                         
 33   ) 4>&1 >&3 3>&- | while read a; do echo "FD4: $a"; done 1>&3 5>&- 6>&-                                
 34  ) 5>&1 >&3 | while read a; do echo "FD5: $a"; done 1>&3 6>&-                                           
 35 ) 6>&1 >&3 | while read a; do echo "FD6: $a"; done 3>&-                                                 
 36                                                                                                         
 37 rm -f /tmp/fifo1 /tmp/fifo2
 38 
 39 
 40 # 对于每个命令和子shell, 分别指出每个fd的指向. 
 41 
 42 exit 0