{
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>&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
。
结果: 在上述情况下,缓冲是由作者决定的。
答案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'
产生延迟首先,然后是foo
和bar
,但这有点特殊情况。)