開發的時候曾經遇到一個情況,需要在 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 將某個指令的標準輸出,導向、存入某個檔案中。

name 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

Reference