重定向的 stderr 在 bash 中是否保持未缓冲状态?

重定向的 stderr 在 bash 中是否保持未缓冲状态?
{
echo bla1
echo bla2 1>&2
} >>myfile 2>&1

两个-s有什么区别吗echo

我能想到的唯一区别是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

结果: 在上述情况下,缓冲是由作者决定的。

答案1

通常出现的输出缓冲是 C 运行时库的属性,与任何重定向或 shell 无关。

当进程启动时,它只是获取一些文件描述符。然后,运行时库构建内部结构及其相关的缓冲(如果有)。它所关心的是 fd 1(用于 stdout)是否连接到终端:如果是,则进行行缓冲,如果不是,则进行块缓冲。无论如何,Stderr 都是无缓冲的。主要与性能有关,写入输出的系统调用相对昂贵,并且如果输出进入文件或管道,则假设带宽比延迟更重要。

当然,缓冲行为也可能取决于实用程序,并且某些工具很可能会自行重新实现 C 库行为。 (我认为像 Perl 和 Python 这样的东西可能会这样做,但据我所知,它们无论如何都遵循惯例。)对于假设不成立的情况,一些实用程序还具有控制缓冲的选项(例如,--line-buffered至少使用 GNU 和 FreeBSD grep),并且有一些工具可以在自己无法执行此操作的工具上强制/欺骗,请参阅:关闭管道中的缓冲

不管怎样,输出echo可能永远不会被缓冲,原因很简单,如果它是一个外部命令,它必须在返回之前刷新它的缓冲区。尽管处理自身的 shellecho可以缓冲它(*)

要使用 Perl 作为测试工具,这将在延迟后立即打印所有输出(块缓冲):

$ perl -e '打印“foo\n”;睡觉2;打印“条\n”;' | sed -e 's/^/:/'
[延迟...]
:富
:酒吧

这将立即打印这些行,中间有一个睡眠:

$ perl -e '打印 STDERR "foo\n";睡觉2;打印 STDERR "bar\n";' 2>&1 | sed -e 's/^/:/'
:富
[延迟...]
:酒吧

(这里的重点sed是验证输出是否通过管道而不是通过它到达终端。)

您可以使用简单的 C 程序执行相同的操作。

在你的问题中,我不确定重定向echo bla2 2>&1应该做什么,因为它echo打印到stdout,fd 1,所以它不会触及重定向的文件描述符。如果我们以相反的方式执行此操作,somecommand 1>&2则该命令仍将写入其自己的标准输出,并具有相关的缓冲。

就像这里(这次使用行缓冲):

$ perl -e '打印“foo”;睡觉2;打印“条\n”;' 1>&2
[延迟...]
富巴

当然,echo这不是唯一一个在退出时刷新缓冲区的工具,如果它们在触发缓冲区刷新之前退出,您将得到与任何其他工具相同的结果。例如,这将分两部分打印,即使两个perl部分都使用行缓冲:

$ perl -e '打印“foo”';睡觉2; perl -e '打印“bar\n”;'[延迟...]酒吧

(* 和 ksh 在某些情况下似乎会缓冲。基本上它会优化诸如echo foo; echo bar;一次write()调用之类的东西,除非中间有很多其他东西。除了我拥有的版本,延迟循环将显示该行为。类似的东西会 ksh -c 'echo foo; i=0; while [ "$i" -lt 234567 ]; do i=$((i + 1)); done; echo bar'产生延迟首先,然后是foobar,但这有点特殊情况。)

相关内容