由Stephane Chazelas编写, 本书作者修订
一个命令期望前3个文件描述符是可用的. 第一个, fd 0(标准输入, stdin), 用作读取. 另外两个, (fd 1, stdout和fd 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 |