重定向的 stderr 在 bash 中是否保持無緩衝?
{ 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>&1
fd1 和 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
不是唯一一個在退出時刷新緩衝區的工具,如果它們在觸發緩衝區刷新之前退出,你會得到與任何其他工具相同的結果。例如,這將分兩部分列印,即使兩個perl
s 都使用行緩衝:$ 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'
給出延遲的東西首先,然後是foo
andbar
,但這有點極端。)