Debian · Linux

在 dash 上面同時 redirect stdout/ stderr 的三兩事

shell script 是我們在解決一些工作不可或缺的利器.

而在 Debian/ Ubuntu 的環境下, 預設的 /bin/sh 指向到 dash. 一個嚴格遵守 POSIX標準的 shell.

2017-01-05_105143.png

以往的 bash 環境下, 若要同時 redirect stdout/ stderr 到 /dev/null 裡, 不顯示任何結果的指令很簡單, 可參考下列程式

[code language=”bash”]
#!/bin/sh

ooxx()
{
echo "Hello stdout"
echo "Hello stderr" 1>&2
}

ooxx &> /dev/null
[/code]

這邊要注意的是

0: 代表 stdin

1: 代表 stdout

2: 代表 stderr

只要使用 &> /dev/null 即可將 stdout/ stderr 重新導向 /dev/null, 因此執行 script 不會有任何輸出. 都被 /dev/null 吃掉了~ 如下圖

2017-01-05_113525

但其實這樣的寫法不符合 POSIX 標準. 我們來看一下, 同樣的指令, 如果在 dash 環境下執行會發生什麼事

2017-01-05_113711

可以看到 dash 不會將 stdout/ stderr 導到 /dev/null. 而是全部印出來.

好, 所以我們改寫一下程式碼, 用更嚴謹的寫法來做重新導入的動作

[code language=”bash”]
#!/bin/sh

ooxx()
{
echo "Hello stdout"
echo "Hello stderr" 1>&2
}

ooxx 2>&1 > /dev/null
[/code]

上述程式先將 stderr 導入 stdout, 接著再將這些結果導入 /dev/null.

note: 這邊要注意一下, 如果要導入的接收方是 “fd”, 要在前面加 “&”, 否則 shell 會將結果輸出到檔案 “1”, 而不是代表 stdout 的 fd: 1

執行結果如下:

2017-01-05_114243.png

這種寫法還是只能將 stdout 導到 /dev/null, 為什麼 stderr 不會一併進去呢 ? 我們不是已經做了導入的動作了嗎 ?

為了確認不是 dash 的問題, 所以換成 bash 重新執行一次程式.

2017-01-05_141708

結果發現不論 dash or bash 都會有相同的問題 – 2>&1 > /dev/null 只會把 stdout 導入到 /dev/null.

依邏輯來說, 指令由左到右循序執行 : stderr 導到 stdout 接著再導入 /dev/null, 不應該會漏掉 stderr 呀?

於是man 了 bash , 發現裡面可以找到詳細說明

2017-01-05_141414.png

裡面寫到, 如果要同時導入 stdout/ stderr, 應使用 > /dev/null 2>&1 的寫法.

根據裡面的說明, 可瞭解執行指令前會預先處理 input/ output. 再從左至右循序執行指令

2017-01-05_142456.png

依照這樣的邏輯就很好解釋下列情型.

  1. ooxx 2>&1 > /dev/null
    • stderr 會先導到 stdout, 也就是顯示在 user terminal. 然後 stdout 才會導到 /dev/null. 所以說 stderr 會被輸出!
  2. ooxx > /dev/null 2>&1
    • 先把 stdout 導入 /dev/null, 然後再將 stderr 導入 stdout. 因為同個 fd, 此時 stdout 已經指向 /dev/null. 所以 stderr 就會導入 /dev/null

我們再根據這樣的邏輯, 來改寫程式. 如下:

[code language=”bash”]
#!/bin/sh

ooxx()
{
echo "Hello stdout"
echo "Hello stderr" 1>&2
}

ooxx > /dev/null 2>&1
[/code]

這樣的寫法, 無論在 bash or dash 的執行環境, 都能夠成功將 stdout/ stderr 都導入 /dev/null. 如下圖所示:
2017-01-05_145730.png

那~除此之外, 還有沒有其他方法呢?

我們來 man dash, 看一下 redirect 相關章節. 其中有一行提到如何關閉 output

[n]>&-      Close standard output (or n).

根據這行的提示, 我們再來改寫一下程式,

[code language=”bash”]
#!/bin/sh

ooxx()
{
echo "Hello stdout"
echo "Hello stderr" 1>&2
}

ooxx 2>&- > /dev/null
[/code]

執行結果如下:

2017-01-05_133505.png

的確都將 stdout/ stderr 的輸出關閉

因為 ooxx 2>&- > /dev/null 執行順序為

1. 先關閉 stderr 的輸出,

2. 將 stdout 導入 /dev/null

因此不會顯示任何輸出.

但如果我們想要將 stderr & stdout 導入特定檔案, 而不是 /dev/null, 就不能使用此方法了


總言之, 如果想要在 Debian/ Ubuntu 環境下進行redirect stdout/ stderr 到 /dev/null, 有三種方法

  1. 直接指定 bash 執行 script, 不使用預設的dash,  即可使用 “&> /dev/null
  2. 使用 “> /dev/null 2>&1
  3. 使用 “2>&- > /dev/null“. (此方法只適用於將結果導入 /dev/null)

ref:

[1]: http://mywiki.wooledge.org/BashFAQ/055

[2]: bash manpage

[3]: dash manpage

發表迴響