输出(standard I/O)可能是软件设计原则里最重要的概念了。这个概念就是:程序应该有数据的来源端、数据的目的端(输出结果的地方)已经报告问题的地方,它们分别被称为标准输入(standard input)、标准输出(standard output)以及标准错误输出(standard error)。程序不必知道也不用关心它的输入与输出背后是什么设备,当程序运行时,这些标准 IO 就已经打开并准备就绪了。
文件"处于打开状态:
文件就是进程默认的标准输入、标准输出和标准错误输出。每个打开的文件都会被分配一个文件描述符。stdin,stdout 和 stderr 的文件描述符分别是 0,1 和 2(一个文件描述符说白了就是文件系统为了跟踪这个打开的文件而分配给它的一个数字)。对于 Bash 进程来说,stdin 默认就是键盘,stdout 和 stderr 默认都是屏幕。
重定向简单理解为:文件、命令、程序或脚本的输出,然后将这些输出作为输入发送到另一个文件、命令、程序或脚本中。
重定向的基本概念
输出重定向到文件 和 >> 符号把标准输出重定向到文件中 会覆盖掉已经存在的文件中的内容> 则把新的内容追加到已经存在的文件中的内容的尾部
文件 log.txt 变为一个空文件(就是 size 为 0):
输出的结果追加到 log.txt 文件的尾部:
输出的文件描述符 1 的写法,完整的写法为:
错误输出重定向到文件错误的文件描述符为 2,所以重定向标准错误到文件的写法为:
输出和标准错误输出重定向到同一个文件中,可以使用下面的方法:
重定向到另一个文件描述符输出和标准错误输出重定向到同一个文件中:
文件描述符而不是文件。
N文件描述符,如果没有明确指定的话默认为 1。文件名。文件描述符 "M" 被重定向到文件 "N"。&N文件描述符,如果没有明确指定的话默认为 1。文件描述符。
关闭文件描述符文件描述符是可以关闭的,典型的写法有下面几种:关闭输入文件描述符 n关闭 stdin&- # 关闭输出文件描述符 n&-,>&- # 关闭 stdout
/dev/null 2>&1 和 2>&1 >/dev/null
文件 /dev/null,发送给它的任何内容都会被丢弃,因此如果需要丢弃标准输出和标准错误输出的内容时可以使用下面的方法:/dev/null 2>/dev/null/dev/null 2>&1
/dev/null/dev/null 的作用是将标准输出 1 重定向到 /dev/null 中,因此标准输出中的内容被完全丢弃了。
&1&1 用到了重定向绑定,采用 & 可以将两个输出绑定在一起,也就是说错误输出将会和标准输出输出到同一个地方。
Linux 在执行 shell 命令之前,就会确定好所有的输入输出位置,解释顺序为从左到右依次执行重定向操作。所以 >/dev/null 2>&1 的作用就是让标准输出重定向到 /dev/null 中,接下来因为错误输出的文件描述符 2 被重定向绑定到了标准输出的描述符 1,所以错误输出也被定向到了 /dev/null 中,错误输出同样也被丢弃了。
&1 >/dev/null&1 >/dev/null 执行的结果和 >/dev/null 2>&1 是不一样的!它的执行过程为:
重定向
重定向到文件中。 从这句开始,所有的 stdin 就都来自于这个文件了,而不是标准输入(通常都是键盘输入)。 这样就提供了一种按行读取文件的方法。当然,也可以用这种方法来重定向标准输出和标准错误输出。下面的 upper.sh 程序把输入文件中的字母转换为大写并输出到另外一个文件中,脚本中使用 exec 同时重定向了 stdin 和 stdout:
E_WRONG_ARGS=<span style="color: #800080">71
<span style="color: #0000ff">if [ ! -r <span style="color: #800000">"<span style="color: #800000">$1<span style="color: #800000">"<span style="color: #000000"> ] # 判断指定的输入文件是否可读
<span style="color: #0000ff">then
<span style="color: #0000ff">echo <span style="color: #800000">"<span style="color: #800000">Can't read from input file!<span style="color: #800000">"
<span style="color: #0000ff">echo <span style="color: #800000">"<span style="color: #800000">Usage: $0 input-file output-file<span style="color: #800000">"<span style="color: #000000">
exit $E_FILE_ACCESS
<span style="color: #0000ff">fi # 即使输入文件($<span style="color: #800080">1<span style="color: #000000">)没被指定
<span style="color: #0000ff">if [ -z <span style="color: #800000">"<span style="color: #800000">$2<span style="color: #800000">"<span style="color: #000000"> ]
<span style="color: #0000ff">then
<span style="color: #0000ff">echo <span style="color: #800000">"<span style="color: #800000">Need to specify output file.<span style="color: #800000">"
<span style="color: #0000ff">echo <span style="color: #800000">"<span style="color: #800000">Usage: $0 input-file output-file<span style="color: #800000">"<span style="color: #000000">
exit $E_WRONG_ARGS
<span style="color: #0000ff">fi<span style="color: #000000">
exec <span style="color: #800080">4<&<span style="color: #800080">0<span style="color: #000000"> # 保存默认 stdin
exec < $<span style="color: #800080">1<span style="color: #000000"> # 将会从输入文件中读取.
exec <span style="color: #800080">7>&<span style="color: #800080">1<span style="color: #000000"> # 保存默认 stdout
exec > $<span style="color: #800080">2<span style="color: #000000"> # 将写到输出文件中.
假设输出文件是可写的
-----------------------------------------------
<span style="color: #0000ff">cat - | <span style="color: #0000ff">tr a-z A-<span style="color: #000000">Z # 转换为大写
从 stdin 中读取
写到 stdout 上
然而,stdin 和 stdout 都被重定向了
-----------------------------------------------<span style="color: #000000">
exec <span style="color: #800080">1>&<span style="color: #800080">7 <span style="color: #800080">7>&-<span style="color: #000000"> # 恢复 stout
exec <span style="color: #800080">0<&<span style="color: #800080">4 <span style="color: #800080">4<&-<span style="color: #000000"> # 恢复 stdin
恢复之后,下边这行代码将会如预期的一样打印到 stdout 上
<span style="color: #0000ff">echo <span style="color: #800000">"<span style="color: #800000">File \"$1\" written to \"$2\" as uppercase conversion.<span style="color: #800000">"<span style="color: #000000">
exit <span style="color: #800080">0
输出重定向到文件:myfile文件 myfile 打开,并且将文件描述符 j 分配给它。 文件 myfile 不存在,那么就创建它。文件描述符 j 没指定,那默认是 fd 0,即标准输入。 内容写到一个文件中指定的地方,比如下面的 demo:
重定向 IO 有效的规避了子 shell 问题:
<span style="color: #0000ff">if [ -z <span style="color: #800000">"<span style="color: #800000">$1<span style="color: #800000">"<span style="color: #000000"> ]
<span style="color: #0000ff">then
<span style="color: #0000ff">echo <span style="color: #800000">"<span style="color: #800000">Usage: $0 input-file<span style="color: #800000">"<span style="color: #000000">
exit $E_WRONG_ARGS
<span style="color: #0000ff">fi<span style="color: #000000">
Lines=<span style="color: #800080">0
<span style="color: #0000ff">cat <span style="color: #800000">"<span style="color: #800000">$1<span style="color: #800000">" | <span style="color: #0000ff">while<span style="color: #000000"> read line; # 管道会产生子 shell
<span style="color: #0000ff">do<span style="color: #000000"> {
<span style="color: #0000ff">echo<span style="color: #000000"> $line
(( Lines++<span style="color: #000000"> )); # 增加这个变量的值
但是外部循环却不能访问
}
<span style="color: #0000ff">done
<span style="color: #0000ff">echo <span style="color: #800000">"<span style="color: #800000">Number of lines read = $Lines<span style="color: #800000">" # <span style="color: #800080">0<span style="color: #000000">
错误!
<span style="color: #0000ff">echo <span style="color: #800000">"<span style="color: #800000">------------------------<span style="color: #800000">"<span style="color: #000000">
exec <span style="color: #800080">3<> <span style="color: #800000">"<span style="color: #800000">$1<span style="color: #800000">"
<span style="color: #0000ff">while read line <&<span style="color: #800080">3
<span style="color: #0000ff">do<span style="color: #000000"> {
<span style="color: #0000ff">echo <span style="color: #800000">"<span style="color: #800000">$line<span style="color: #800000">"<span style="color: #000000">
(( Lines++<span style="color: #000000"> )); # 增加这个变量的值
现在外部循环就可以访问了
# 没有子shell,现在就没问题了
}
<span style="color: #0000ff">done<span style="color: #000000">
exec <span style="color: #800080">3>&-
<span style="color: #0000ff">echo <span style="color: #800000">"<span style="color: #800000">Number of lines read = $Lines<span style="color: #800000">"<span style="color: #000000">
exit <span style="color: #800080">0
代码保存到 avoid-subshell.sh 文件中,然后运行下面的命令:
重定向 IO 的方式获得了正确的结果。
代码块重定向
代码块也是可以进行 IO 重定向的,下面的 demo 把 while 循环的标准输入重定向到一个文件:
<span style="color: #0000ff">if [ -z <span style="color: #800000">"<span style="color: #800000">$1<span style="color: #800000">"<span style="color: #000000"> ]
<span style="color: #0000ff">then
<span style="color: #0000ff">echo <span style="color: #800000">"<span style="color: #800000">Usage: $0 input-file<span style="color: #800000">"<span style="color: #000000">
exit $E_WRONG_ARGS
<span style="color: #0000ff">fi<span style="color: #000000">
count=<span style="color: #800080">0
<span style="color: #0000ff">while [ <span style="color: #800000">"<span style="color: #800000">$name<span style="color: #800000">" !=<span style="color: #000000"> xxyy ]
<span style="color: #0000ff">do<span style="color: #000000">
read name
<span style="color: #0000ff">if [ -z <span style="color: #800000">"<span style="color: #800000">$name<span style="color: #800000">" ]; <span style="color: #0000ff">then<span style="color: #000000">
break
<span style="color: #0000ff">fi
<span style="color: #0000ff">echo<span style="color: #000000"> $name
let <span style="color: #800000">"<span style="color: #800000">count += 1<span style="color: #800000">"
<span style="color: #0000ff">done <<span style="color: #800000">"<span style="color: #800000">$1<span style="color: #800000">"
<span style="color: #0000ff">echo <span style="color: #800000">"<span style="color: #800000">$count names read<span style="color: #800000">"<span style="color: #000000">
exit <span style="color: #800080">0
代码保存到文件 whileblock.sh 中,然后执行下面的命令,可以看到标准输入重定向后的结果:
重定向了 for 循环的标准输入和标准输出:
<span style="color: #0000ff">if [ -z <span style="color: #800000">"<span style="color: #800000">$1<span style="color: #800000">"<span style="color: #000000"> ]
<span style="color: #0000ff">then
<span style="color: #0000ff">echo <span style="color: #800000">"<span style="color: #800000">Usage: $0 input-file output-file<span style="color: #800000">"<span style="color: #000000">
exit $E_WRONG_ARGS
<span style="color: #0000ff">fi
<span style="color: #0000ff">if [ -z <span style="color: #800000">"<span style="color: #800000">$2<span style="color: #800000">"<span style="color: #000000"> ]
<span style="color: #0000ff">then
<span style="color: #0000ff">echo <span style="color: #800000">"<span style="color: #800000">Usage: $0 input-file output-file<span style="color: #800000">"<span style="color: #000000">
exit $E_WRONG_ARGS
<span style="color: #0000ff">fi<span style="color: #000000">
FinalName=<span style="color: #800000">"<span style="color: #800000">xxyy<span style="color: #800000">"<span style="color: #000000">
line_count=$(<span style="color: #0000ff">wc <span style="color: #800000">"<span style="color: #800000">$1<span style="color: #800000">" | <span style="color: #0000ff">awk <span style="color: #800000">'<span style="color: #800000">{ print $1 }<span style="color: #800000">'<span style="color: #000000">)
<span style="color: #0000ff">for name <span style="color: #0000ff">in $(<span style="color: #0000ff">seq<span style="color: #000000"> $line_count)
<span style="color: #0000ff">do<span style="color: #000000">
read name
<span style="color: #0000ff">echo <span style="color: #800000">"<span style="color: #800000">$name<span style="color: #800000">"
<span style="color: #0000ff">if [ <span style="color: #800000">"<span style="color: #800000">$name<span style="color: #800000">" = <span style="color: #800000">"<span style="color: #800000">$FinalName<span style="color: #800000">"<span style="color: #000000"> ]
<span style="color: #0000ff">then<span style="color: #000000">
break
<span style="color: #0000ff">fi
<span style="color: #0000ff">done < <span style="color: #800000">"<span style="color: #800000">$1<span style="color: #800000">" > <span style="color: #800000">"<span style="color: #800000">$2<span style="color: #800000">"<span style="color: #000000">
exit <span style="color: #800080">0
代码保存到文件 forblock.sh 中,然后执行下面的命令,终端上没有任何输出,因为输出都被重定向到了 out.txt 文件:
重定向的结果是显而易见的。但在一些特殊的用例中结果就不是那么明显了,需要我们理解重定向的基本规则,才能理解哪些特殊写法的含义或是写出我们自己满意的脚本。