背景知識
文件描述符(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
以上的方案更詳細的展示可見這個答案:
這篇文章實際產生於這個答案。從這個答案出發,補充了各類的背景知識,最后充分理解也自行寫出了答主的方案。