默认情况下始终有3个"文件"处于打开状态, stdin (键盘), stdout (屏幕), and stderr (错误消息输出到屏幕上). 这3个文件和其他打开的文件都可以被重定向. 对于重定向简单的解释就是捕捉一个文件, 命令, 程序, 脚本, 或者甚至是脚本中的代码块(参见 Example 3-1 和 Example 3-2)的输出, 然后将这些输出作为输入发送到另一个文件, 命令, 程序, 或脚本中.
每个打开的文件都会被分配一个文件描述符.[1]stdin, stdout, 和stderr的文件描述符分别是0, 1, 和 2. 对于正在打开的额外文件, 保留了描述符3到9. 在某些时候将这些格外的文件描述符分配给stdin, stdout, 或者是stderr作为临时的副本链接是非常有用的. [2] 在经过复杂的重定向和刷新之后需要把它们恢复成正常的样子 (参见 Example 16-1).
1 COMMAND_OUTPUT >
2 # 重定向stdout到一个文件.
3 # 如果没有这个文件就创建, 否则就覆盖.
4
5 ls -lR > dir-tree.list
6 # 创建一个包含目录树列表的文件.
7
8 : > filename
9 # > 会把文件"filename"截断为0长度.
10 # 如果文件不存在, 那么就创建一个0长度的文件(与'touch'的效果相同).
11 # : 是一个占位符, 不产生任何输出.
12
13 > filename
14 # > 会把文件"filename"截断为0长度.
15 # 如果文件不存在, 那么就创建一个0长度的文件(与'touch'的效果相同).
16 # (与上边的": >"效果相同, 但是在某些shell下可能不能工作.)
17
18 COMMAND_OUTPUT >>
19 # 重定向stdout到一个文件.
20 # 如果文件不存在, 那么就创建它, 如果存在, 那么就追加到文件后边.
21
22
23 # 单行重定向命令(只会影响它们所在的行):
24 # --------------------------------------------------------------------
25
26 1>filename
27 # 重定向stdout到文件"filename".
28 1>>filename
29 # 重定向并追加stdout到文件"filename".
30 2>filename
31 # 重定向stderr到文件"filename".
32 2>>filename
33 # 重定向并追加stderr到文件"filename".
34 &>filename
35 # 将stdout和stderr都重定向到文件"filename".
36
37 #==============================================================================
38 # 重定向stdout, 一次一行.
39 LOGFILE=script.log
40
41 echo "This statement is sent to the log file, \"$LOGFILE\"." 1>$LOGFILE
42 echo "This statement is appended to \"$LOGFILE\"." 1>>$LOGFILE
43 echo "This statement is also appended to \"$LOGFILE\"." 1>>$LOGFILE
44 echo "This statement is echoed to stdout, and will not appear in \"$LOGFILE\"."
45 # 每行过后, 这些重定向命令会自动"reset".
46
47
48
49 # 重定向stderr, 一次一行.
50 ERRORFILE=script.errors
51
52 bad_command1 2>$ERRORFILE # 错误消息发到$ERRORFILE中.
53 bad_command2 2>>$ERRORFILE # 错误消息添加到$ERRORFILE中.
54 bad_command3 # 错误消息echo到stderr,
55 #+ 并且不出现在$ERRORFILE中.
56 # 每行过后, 这些重定向命令也会自动"reset".
57 #==============================================================================
58
59
60
61 2>&1
62 # 重定向stderr到stdout.
63 # 得到的错误消息与stdout一样, 发送到一个地方.
64
65 i>&j
66 # 重定向文件描述符i 到 j.
67 # 指向i文件的所有输出都发送到j中去.
68
69 >&j
70 # 默认的, 重定向文件描述符1(stdout)到 j.
71 # 所有传递到stdout的输出都送到j中去.
72
73 0< FILENAME
74 < FILENAME
75 # 从文件中接受输入.
76 # 与">"是成对命令, 并且通常都是结合使用.
77 #
78 # grep search-word <filename
79
80
81 [j]<>filename
82 # 为了读写"filename", 把文件"filename"打开, 并且分配文件描述符"j"给它.
83 # 如果文件"filename"不存在, 那么就创建它.
84 # 如果文件描述符"j"没指定, 那默认是fd 0, stdin.
85 #
86 # 这种应用通常是为了写到一个文件中指定的地方.
87 echo 1234567890 > File # 写字符串到"File".
88 exec 3<> File # 打开"File"并且给它分配fd 3.
89 read -n 4 <&3 # 只读4个字符.
90 echo -n . >&3 # 写一个小数点.
91 exec 3>&- # 关闭fd 3.
92 cat File # ==> 1234.67890
93 # 随机存储.
94
95
96
97 |
98 # 管道.
99 # 通用目的的处理和命令链工具.
100 # 与">"很相似, 但是实际上更通用.
101 # 对于想将命令, 脚本, 文件和程序串连起来的时候很有用.
102 cat *.txt | sort | uniq > result-file
103 # 对所有的.txt文件的输出进行排序, 并且删除重复行,
104 # 最后将结果保存到"result-file"中.
|
可以将输入输出重定向和(或)管道的多个实例结合到一起写在一行上.
1 command < input-file > output-file
2
3 command1 | command2 | command3 > output-file
|
参见
Example 12-28 和
Example A-15.
可以将多个输出流重定向到一个文件上.
1 ls -yz >> command.log 2>&1
2 # 将错误选项"yz"的结果放到文件"command.log"中.
3 # 因为stderr被重定向到这个文件中,
4 #+ 所有的错误消息也就都指向那里了.
5
6 # 注意, 下边这个例子就不会给出相同的结果.
7 ls -yz 2>&1 >> command.log
8 # 输出一个错误消息, 但是并不写到文件中.
9
10 # 如果将stdout和stderr都重定向,
11 #+ 命令的顺序会有些不同. |
关闭文件描述符
- n<&-
关闭输入文件描述符n.
- 0<&-, <&-
关闭stdin.
- n>&-
关闭输出文件描述符n.
- 1>&-, >&-
关闭stdout.
子进程继承了打开的文件描述符. 这就是为什么管道可以工作. 如果想阻止fd被继承, 那么可以关掉它.
1 # 只重定向stderr到一个管道.
2
3 exec 3>&1 # 保存当前stdout的"值".
4 ls -l 2>&1 >&3 3>&- | grep bad 3>&- # 对'grep'关闭fd 3(但不关闭'ls').
5 # ^^^^ ^^^^
6 exec 3>&- # 现在对于剩余的脚本关闭它.
7
8 # Thanks, S.C. |
如果想了解关于I/O重定向更多的细节参见 附录 E.
exec <filename 命令会将stdin重定向到文件中. 从这句开始, 后边的输入就都来自于这个文件了, 而不是标准输入了(通常都是键盘输入). 这样就提供了一种按行读取文件的方法, 并且可以使用sed 和/或 awk来对每一行进行分析.
Example 16-1. 使用exec重定向标准输入
1 #!/bin/bash
2 # 使用'exec'重定向标准输入.
3
4
5 exec 6<&0 # 将文件描述符#6与stdin链接起来.
6 # 保存了stdin.
7
8 exec < data-file # stdin被文件"data-file"所代替.
9
10 read a1 # 读取文件"data-file"的第一行.
11 read a2 # 读取文件"data-file"的第二行.
12
13 echo
14 echo "Following lines read from file."
15 echo "-------------------------------"
16 echo $a1
17 echo $a2
18
19 echo; echo; echo
20
21 exec 0<&6 6<&-
22 # 现在将stdin从fd #6中恢复, 因为刚才我们把stdin重定向到#6了,
23 #+ 然后关闭fd #6 ( 6<&- ), 好让这个描述符继续被其他进程所使用.
24 #
25 # <&6 6<&- 这么做也可以.
26
27 echo -n "Enter data "
28 read b1 # 现在"read"已经恢复正常了, 就是从stdin中读取.
29 echo "Input read from stdin."
30 echo "----------------------"
31 echo "b1 = $b1"
32
33 echo
34
35 exit 0 |
同样的, exec >filename 命令将会把stdout重定向到一个指定的文件中. 这样所有的命令输出就都会发向那个指定的文件, 而不是stdout.
Example 16-2. 使用exec来重定向stdout
1 #!/bin/bash
2 # reassign-stdout.sh
3
4 LOGFILE=logfile.txt
5
6 exec 6>&1 # 将fd #6与stdout相连接.
7 # 保存stdout.
8
9 exec > $LOGFILE # stdout就被文件"logfile.txt"所代替了.
10
11 # ----------------------------------------------------------- #
12 # 在这块中所有命令的输出就都发向文件 $LOGFILE.
13
14 echo -n "Logfile: "
15 date
16 echo "-------------------------------------"
17 echo
18
19 echo "Output of \"ls -al\" command"
20 echo
21 ls -al
22 echo; echo
23 echo "Output of \"df\" command"
24 echo
25 df
26
27 # ----------------------------------------------------------- #
28
29 exec 1>&6 6>&- # 恢复stdout, 然后关闭文件描述符#6.
30
31 echo
32 echo "== stdout now restored to default == "
33 echo
34 ls -al
35 echo
36
37 exit 0 |
Example 16-3. 使用exec在同一脚本中重定向stdin和stdout
1 #!/bin/bash
2 # upperconv.sh
3 # 将一个指定的输入文件转换为大写.
4
5 E_FILE_ACCESS=70
6 E_WRONG_ARGS=71
7
8 if [ ! -r "$1" ] # 判断指定的输入文件是否可读?
9 then
10 echo "Can't read from input file!"
11 echo "Usage: $0 input-file output-file"
12 exit $E_FILE_ACCESS
13 fi # 即使输入文件($1)没被指定
14 #+ 也还是会以相同的错误退出(为什么?).
15
16 if [ -z "$2" ]
17 then
18 echo "Need to specify output file."
19 echo "Usage: $0 input-file output-file"
20 exit $E_WRONG_ARGS
21 fi
22
23
24 exec 4<&0
25 exec < $1 # 将会从输入文件中读取.
26
27 exec 7>&1
28 exec > $2 # 将写到输出文件中.
29 # 假设输出文件是可写的(添加检查?).
30
31 # -----------------------------------------------
32 cat - | tr a-z A-Z # 转换为大写.
33 # ^^^^^ # 从stdin中读取.Reads from stdin.
34 # ^^^^^^^^^^ # 写到stdout上.
35 # 然而, stdin和stdout都被重定向了.
36 # -----------------------------------------------
37
38 exec 1>&7 7>&- # 恢复 stout.
39 exec 0<&4 4<&- # 恢复 stdin.
40
41 # 恢复之后, 下边这行代码将会如期望的一样打印到stdout上.
42 echo "File \"$1\" written to \"$2\" as uppercase conversion."
43
44 exit 0 |
I/O重定向是一种避免可怕的子shell中不可存取变量问题的方法.
Example 16-4. 避免子shell
1 #!/bin/bash
2 # avoid-subshell.sh
3 # Matthew Walker提出的建议.
4
5 Lines=0
6
7 echo
8
9 cat myfile.txt | while read line; # (译者注: 管道会产生子shell)
10 do {
11 echo $line
12 (( Lines++ )); # 增加这个变量的值
13 #+ 但是外部循环却不能存取.
14 # 子shell问题.
15 }
16 done
17
18 echo "Number of lines read = $Lines" # 0
19 # 错误!
20
21 echo "------------------------"
22
23
24 exec 3<> myfile.txt
25 while read line <&3
26 do {
27 echo "$line"
28 (( Lines++ )); # 增加这个变量的值
29 #+ 现在外部循环就可以存取了.
30 # 没有子shell, 现在就没问题了.
31 }
32 done
33 exec 3>&-
34
35 echo "Number of lines read = $Lines" # 8
36
37 echo
38
39 exit 0
40
41 # 下边这些行是脚本的结果, 脚本是不会走到这里的.
42
43 $ cat myfile.txt
44
45 Line 1.
46 Line 2.
47 Line 3.
48 Line 4.
49 Line 5.
50 Line 6.
51 Line 7.
52 Line 8. |
|