背景知识
文件描述符(file descriptor)
『它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符』https://zh.wikipedia.org/wiki/文件描述符
每个Unix进程(除了可能的守护进程)应均有三个标准的POSIX文件描述符,对应于三个标准流:
0 - stdin
1 - stdout
2 - stderr
(以下均用fd指代文件描述符)
bash - 3.6 Redirections
一个重定向可由一系列的 >
, <
, >&
, <&
, >>
构成,从左到右决定了输出最终指向的文件。重定向的符号两端是文件描述符,或者可被expand解析的文件名称。
重定向的解析遵循三个规则:
- 解析顺序从左到右
- 依次解析,右边的不能影响左边的结果
- 在具体command执行之前就解析完整
在redirection - 重定向中,顺序是非常重要的。比如[manual](https://www.gnu.org/software/bash/manual/html_node/Redirections.html)中的一个例子是:
ls > dirlist 2>&1
和
ls 2>&1 > dirlist
的结果不同。
manual中的解释为,后者"the standard error was made a copy of the standard output before the standard output was redirected to dirlist."
http://stackoverflow.com/a/34326455/6081699 这位答主给出了详尽的解释。回顾重定向的三个原则(也是来自这位答主的整理):
- 解析顺序从左到右
- 依次解析,右边的不能影响左边的结果
- 在具体command执行之前就解析完整
在第二条中,2>&1
将stderr
的输出导向了stdout
的输出,然而stdout
的输出还没有被重定向,依然指向默认输出地点 - 终端tty。之后stdout
才被重定向到dirlist
,而由于重定向不受之后的规则影响的性质,stderr
则继续指向终端tty。
这位答主继续解释了manual中的copy一词的含义:
技术上讲,`stdout`的fd被复制给了`stderr`用来让它指向,因此这就解释了后面定义的重定向规则不会影响之前定义的重定向规则。
因此,要写一条完整有效的重定向链,可以采用一个形象化的技巧:将重定向链看做
-> -> -> -> -> ... ->->->
的链条的话,与redirection从左到右的解析顺序正相反,我们应该从最右端开始写起,这样->
左端的文件流就依然能够被下一个->
右端的文件流所指向。
现在
我们可以实现题目中的要求 - 『将正常输出和错误信息保存到日志文件,同时在终端输出』
如果要把重定向规则写入到.sh文件中,则要在开头使用exec
语句:
### bash - [Read and exec](http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_08_02.html#sect_08_02_03)
exec fd_number> file
表示将fd_number
文件描述符指向的文件指向file
作为输出文件。
如果直接在命令行中使用,则采用
$ <some_cmd> <redirection>
的语法。
『将正常输出和错误信息保存到日志文件,同时在终端输出』描述了一个这样的重定向链:
由于指向终端的fd只能通过复制stdout
的fd得到,在重定向stdout
之前,应该将它的文件描述符保存下来,然后再将stdout
指向log_file
并将stderr
接入链条的末端。写为:
exec 3>&1 1>&{logfile} 2>&1
这样我们就用fd 3保存了对终端tty的输出,在之后的使用中想要将一些命令输入到终端显示,就可以手动:
<some_cmd> >&3
以上的方案更详细的展示可见这个答案:
这篇文章实际产生于这个答案。从这个答案出发,补充了各类的背景知识,最后充分理解也自行写出了答主的方案。