Bash

重定向的 stderr 在 bash 中是否保持無緩衝?

  • September 16, 2021
{
echo bla1
echo bla2 1>&2
} >>myfile 2>&1

echo這兩個-s有什麼區別嗎?

我能想到的唯一區別是,如果echo bla2 2>&1保留來自 stderr 的無緩衝屬性。

怎麼樣

{
echo bla1
echo bla2 1>&2 &
} >>myfile 2>&1

?

是不是一樣

{
echo bla1
echo bla2 &
} >>myfile 2>&1

?

之後{...} >>myfile 2>&1fd1 和 fd2 本質上會變成同一個東西,還是它們會保留過去的任何東西?

編輯:

基於@ilkkachu 的答案,我嘗試了以下方法:

perl -e 'print "foo"; sleep 2; print "bar\n"; sleep 2; print "bla"'
# ^^^line buffered
perl -e 'print "foo"; sleep 2; print "bar\n"; sleep 2; print "bla"' | cat
# ^^^block buffered
touch myfile; tail -f myfile &
perl -e 'print "foo"; sleep 2; print "bar\n"; sleep 2; print "bla"' >>myfile 2>&1
# ^^^block buffered
perl -e 'print STDERR "foo"; sleep 2; print STDERR "bar\n"; sleep 2; print STDERR "bla"'
# ^^^unbuffered
perl -e 'print STDERR "foo"; sleep 2; print STDERR "bar\n"; sleep 2; print STDERR "bla"' 2>&1 | cat
# ^^^unbuffered
perl -e 'print STDERR "foo"; sleep 2; print STDERR "bar\n"; sleep 2; print STDERR "bla"' >>myfile 2>&1
# ^^^unbuffered

在 fd1 的情況下,根據輸出類型調整緩衝。

在 fd2 的情況下,輸出無關緊要。

{ echo -n foo; sleep 2; echo bar; sleep 2; echo -n bla; }
# ^^^unbuffered
{ echo -n foo; sleep 2; echo bar; sleep 2; echo -n bla; } | cat
# ^^^unbuffered
touch myfile; tail -f myfile &
{ echo -n foo; sleep 2; echo bar; sleep 2; echo -n bla; } >>myfile 2>&1
# ^^^unbuffered
{ echo -n foo 1>&2; sleep 2; echo bar 1>&2; sleep 2; echo -n bla 1>&2; }
# ^^^unbuffered
{ echo -n foo 1>&2; sleep 2; echo bar 1>&2; sleep 2; echo -n bla 1>&2; } | cat
# ^^^unbuffered
{ echo -n foo 1>&2; sleep 2; echo bar 1>&2; sleep 2; echo -n bla 1>&2; } >>myfile 2>&1
# ^^^unbuffered

似乎,echo總是刷新,不管輸出去哪裡。

實驗重複了

{ printf foo; sleep 2; printf 'bar\n'; sleep 2; printf bla; }

同樣,結果與 相同echo

結果: 在上述情況下,緩衝是由作者決定的。

通常出現的輸出緩衝是 C 執行時庫的屬性,與任何重定向或 shell 無關。

當程序啟動時,它只會獲取一些文件描述符。然後執行時庫建構內部結構及其相關的緩衝(如果有的話)。它所關心的只是 fd 1 (用於標準輸出)是否連接到終端:如果是,則將其設為行緩衝,如果不是,則將其設為塊緩衝。無論如何,Stderr 都是無緩衝的。主要是關於性能,寫入輸出的系統呼叫相對昂貴,如果輸出到文件或管道,則假設頻寬對延遲更重要。

當然,緩衝行為也可能取決於實用程序,並且某些工具可能會自行重新實現 C 庫行為。(我認為 Perl 和 Python 之類的東西可能會這樣做,但據我所知,它們無論如何都遵循慣例。)對於假設不成立的情況,一些實用程序還具有控制緩衝的選項(例如--line-buffered,至少使用 GNU 和 FreeBSD grep),並且有一些工具可以強制/欺騙自己無法做到的工具,請參閱:關閉管道中的緩衝

無論如何,來自的輸出echo可能永遠不會被緩衝,原因很簡單,如果它是一個外部命令,它必須在返回之前刷新它的緩衝區。雖然處理echo自己的外殼可以緩衝它(*)。

要將 Perl 用作測試工具,這將在延遲(塊緩衝)後立即列印所有輸出:

$ perl -e 'print "foo\n"; 睡覺 2; 列印“條\n”;' | sed -e 's/^/:/'
*[延遲...]*
:foo
:酒吧

這將立即列印這些行,中間有一個睡眠:

$ perl -e '列印 STDERR "foo\n"; 睡覺 2; 列印 STDERR "bar\n";' 2>&1 | sed -e 's/^/:/'
:foo
*[延遲...]*
:酒吧

sed那裡的重點是驗證輸出通過管道而不是通過管道到達終端。)

你可以用一個簡單的 C 程序來做同樣的事情。

在你的問題中,我不確定重定向echo bla2 2>&1應該做什麼,因為它echo列印到標準輸出,fd 1,所以它不會觸及重定向的文件描述符。如果我們反其道而行之somecommand 1>&2,那麼該命令仍將寫入其自己的標準輸出,並帶有相關的緩衝。

就像這裡(這次有行緩衝):

$ perl -e 'print "foo"; 睡覺 2; 列印“條\n”;' 1>&2
*[延遲...]*
富吧

當然echo不是唯一一個在退出時刷新緩衝區的工具,如果它們在觸發緩衝區刷新之前退出,你會得到與任何其他工具相同的結果。例如,這將分兩部分列印,即使兩個perls 都使用行緩衝:

$ perl -e '列印“foo”'; 睡覺 2; perl -e '列印 "bar\n";'
foo *[延遲...]* bar

(* 並且 ksh 在某些情況下似乎是緩衝的。基本上它優化echo foo; echo bar;了一次write()呼叫之類的事情,除非兩者之間幾乎有其他任何東西。除了在我擁有的版本上,延遲循環會顯示行為。像 ksh -c 'echo foo; i=0; while [ "$i" -lt 234567 ]; do i=$((i + 1)); done; echo bar'給出延遲的東西首先,然後是fooand bar,但這有點極端。)

引用自:https://unix.stackexchange.com/questions/669156