到底怎麼樣可以同時把std error, std out 記錄到檔案中呢?

錯誤記錄是很重要的

身為一個專業的工程師,當程式出現錯誤時,一定要把錯誤好好的記錄下來,而要能夠把錯誤有彈性的記錄下來,在Java上請使用log4j,在Python上請好好研究logging module, 在C 上有log4j的兄弟: log4cxx。若不好好善用這些logging module, 你所寫出來的服務是絕對沒有辨法在Production環境下生存的。

那吐出 std error 的那些壞孩子呢?

每個人都有不得已的時候,總是有許多的程式是不得不把訊息送往std out, 及 std error. 比如說一些簡短 Shell Script 或是較老舊的程式碼。
那我們只好另外把這些訊息集中處理。


這樣的工作看似不難,工程師跟Shell 一定有點交情,IO Redirection 多少都有交手過幾次,直覺可能會寫出類似下面的指令:

some_command 2>&1 >/var/log/certain_message

字面上直接翻譯: “我想把std error 導到 std out, 再把  std out 導到檔案中”。但是事情絕對沒有那簡單,上面的指令是不work的。
你可以用下面這個指令來認清事實:
python -c “from sys import *; stderr.write(“err”); stdout.write(“out”)” 2>&1  > /tmp/o.txt
這個指令在沒有IO Redirect前,會送 “err” 這個字串到 std error ,送 “out” 到 std out。而在設定完IO Redirection後,err這串字會竟然還是只會出現在畫面上,而out 會出現在 /tmp/o.txt 這個檔案裡。唉,事與願違,錢不好賺。

我該怎麼做

如果你是個懶鬼,我就跟你說,換順序就好,本篇文章可以不用看下去了,但記得給個讚之類的東西。
some_command >/var/log/certain_message 2>&1
上面這段 Command 就可以把所有的訊息收到集起來了,把 2>&1 往後擺即可。但要能夠理解順序的影響,就要記得一個Process 與其所掌握IO Device 之間隔了一層抽象層: File Descriptor. Process 是經由 File Descriptor 才能對 IO Device 有所影響。我們先以這個簡短的command 來做圖解釋: echo “cnt” 2>&1 > o.txt 。
並且請記得,預設情況下,FD1是指向 std out,  FD2是指向 std error下圖一是我們還沒有做任何 IO Redirection 以前的樣貌。

圖一: File Descriptor 與 IO Device 的對應

Bash 的man page 有說到,會由左到右分析指令。
當以在圖二中,Shell 會首先分析  2>&1 ,這會讓FD 2 指向  FD 1 所指向的裝置。因此,FD2會指向 std out. 而 std err 就像是個 dangling pointer,不再有機會被使用到了。

圖二: 2 指向了 std out, 而此時, std error 成了孤兒了

下圖三: 下一個IO Redirection:  > o.txt, 使 FD1指向了 o.txt.
這只改變FD1, 不會對 FD 2 造成任何影響, FD2 還是指向 std out。
這樣子的結果,達不到我們想要收集訊息的目地。

圖三: IO Redirection 設定結束了,只有FD1 被收集到了檔案中

那如下圖四的正確順序指令 echo “cnt” > o.txt  2>&1 又會呈現什麼樣貌呢?

圖四: 正確順序的指令又會呈現什麼樣貌呢?

Shell 首先面對了 > o.txt, 會把 FD1 指向 o.txt.

圖五: FD1 被指向了 o.txt

在下圖六,Shell 遇到了 2>&1, 會把FD2 指向 FD1 所指向的IO Device, 也就是 o.txt。

最後,我們的Process 對FD1及FD2 的output, 都會直接導向了 o.txt.結論
上面這些圖,是幫助我們理解Shell 背後的運作,只要記住 file descriptor 那一層間接性,我們就可以清楚地推導出 io redirection 所造成的結果而不用去硬記指令的寫法,如果你想寫出更複雜的指令組合,也絕不會出錯。 如果喜歡這類文章,你在這個Blog可以看到加入 icoding 粉絲團的方式,請不吝惜給我們支持與鼓勵!


Comments
  1. 回覆
  2. 回覆

Leave a Reply

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *