2013年3月4日 星期一

關於 Bash 的 Redirection 使用的心得

一般常見的方式是把標準輸出 (stdout) 訊息跟標準錯誤 (stderr) 訊息全部都導進一個檔案裡面,例如以下指令:

$ sudo fdisk -l >fdisk.log 2>&1

如果是寫在一個 Shell Script 的檔案裡面,就可以寫成像是下面這樣:

#!/bin/sh

exec >fdisk.log # 0 (stdin) 1 (fdisk.log) 2 (stderr)
exec 2>&1       # 0 (stdin) 1 (fdisk.log) 2 (fdisk.log)

sudo fdisk -l

但是其實我們也可以使用更複雜的 Redirection,從 bash need STDOUT+STDERR in log, and STDERR to its normal destination 改出來的例子:

$ ((sudo fdisk -l 3>&1 1>&2 2>&3- | tee /dev/fd/2) 3>&1 1>&2 2>&3-) | cat >fdisk.log

這個例子的結果是將標準輸出訊息跟標準錯誤訊息都導進 fdisk.log 裡面,然後將標準錯誤訊息顯示出來。

但是它做了什麼事情?

首先是

sudo fdisk -l 3>&1 1>&2 2>&3-

如果直接用 Shell Script 檔案來寫,會看起來像是這樣:

#!/bin/sh

exec 3>&1  # 0 (stdin) 1 (stdout) 2 (stderr) 3 (stdout)
exec 1>&2  # 0 (stdin) 1 (stderr) 2 (stderr) 3 (stdout)
exec 2>&3- # 0 (stdin) 1 (stderr) 2 (stdout) 3 (closed)

sudo fdisk -l

看起來像是

`sudo fdisk -l` --> stdout ` '-> stdout
                |           X
                `-> stderr ' `-> stderr

它將標準輸出訊息跟標準錯誤訊息互換了,接下來

| tee /dev/fd/2
會收到 pipe 符號前面傳來的標準輸出訊息也就是 `sudo fdisk -l` 的 2 (stdout) 會變成 `tee` 的 0 (stdin),然後 `tee` 會將其內容導到 `tee` 的 2 (stderr) 並且顯示在 `tee` 的 1 (stdout) 於是乎 `sudo fdisk -l` 的標準輸出訊息跟標準錯誤訊息都會被導到 stderr 而標準錯誤訊息則被導到 stdout

看起來像是

                                       `tee`
`sudo fdisk -l` --> stdout ` '-> stdout --> stdout
                |           X           `-> stderr
                `-> stderr ' `-> stderr

接下來再經歷一次的標準輸出訊息跟標準錯誤訊息互換

((...) 3>&1 1>&2 2>&3-)

看起來像是

                                       `tee`
`sudo fdisk -l` --> stdout    -> stdout --> stdout ` '-> stdout
                |          ` '          |           X
                |           X           `-> stderr ' `-> stderr
                `-> stderr ' `-> stderr --> stdout

從最後一張圖就可以看出最後的兩個 stdout 分別來自於 `sudo fdisk -l` 的 stdout 跟 stderr 而最後的一個 stderr 還是來自於 `sudo fdisk -l` 的 stderr

最後一步就是透過 `| cat >fdisk.log` 將兩個 stdout 都導進 fdisk.log 檔案就大功告成啦!:D