開發的時候曾經遇到一個情況,需要在 shell 執行程式時將 stderr 導向 console,但是不能顯示stdout 的訊息,且必須同時 stderr 和 stdout 記錄到 log 檔案。
文件描述符 (file descriptor)
在 UNIX/Linux 系統中,是使用非負整數(0, 1, 2, …)來表示文件描述符(file descriptor, fd),用於標示系統中打開的文件和資源。 系統預設打開 0 (stdin), 1 (stdout), 2 (stderr),也是我們平常常用到的描述符。
接下來會將需求實作出來,並舉幾個例子以及需要注意的地方:
- 藉由ls一個不存在的檔案來測試,並將 stderr 訊息導向 error.log,我們cat檔案可以看到錯誤訊息:
ls: cannot access foo.txt: No such file or directory
。
ls foo.txt 2>error.log
- 將 stderr 導向 stdout,指令中 &(ampersand)符號是告訴shell我們把 fd 2 導向 fd 1 這個fd而不是一個檔案名稱。 在linux下,一切皆檔案,所以系統背後所做的事情是將 fd 1 複製給 fd 2。
ls foo.txt 2>&1
- 以下的指令先將 stdout 導向 error.log,所以之後 2>&1 重導向會將錯誤訊息寫入 syslog.log 中。 (一般
1>syslog.log
會省略1寫成>error.log
)
ls foo.txt 1>syslog.log 2>&1
- 這個指令和剛剛指令很像,要特別注意的是,有些人可能會理解成 「先把 stderr 導向 stdout,後面再把 stdout 導向 error.log,所以 stderr 和 stdout 都會寫入 syslog.log」。
ls foo.txt 2>&1 1>syslog.log
實際上則是 fd 2 複製原本的 fd 1 指向它目前指向的位置也就是 console ,之後 fd 1 會被導向到 syslog.log ,但 fd 2 所複製的副本不受影響,所以錯誤訊息會被印到 console ,stdout 會被存入檔案。
Tee
到這裡已經實作出需求的一半了,但是錯誤訊息尚未被存入檔案,這時候我們可以用 tee 將某個指令的標準輸出,導向、存入某個檔案中。
ls -l的輸出被導向 tee,並且複製到檔案 file.txt 以及下一個命令
(圖片來源: https://zh.wikipedia.org/wiki/Tee)
- 可以看到 stderr 輸出到 console 並用管線將 console 內容傳給 tee 存入檔案。因此該指令也就能達成我們原本的需求。
ls foo.txt 2>&1 1>syslog.log | tee syslog.log