{
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 用作测试工具,这将在延迟(块缓冲)后立即打印所有输出:
这将立即打印这些行,中间有一个睡眠:
(
sed
那里的重点是验证输出通过管道而不是通过管道到达终端。)你可以用一个简单的 C 程序来做同样的事情。
在你的问题中,我不确定重定向
echo bla2 2>&1
应该做什么,因为它echo
打印到标准输出,fd 1,所以它不会触及重定向的文件描述符。如果我们反其道而行之somecommand 1>&2
,那么该命令仍将写入其自己的标准输出,并带有相关的缓冲。就像这里(这次有行缓冲):
当然
echo
不是唯一一个在退出时刷新缓冲区的工具,如果它们在触发缓冲区刷新之前退出,你会得到与任何其他工具相同的结果。例如,这将分两部分打印,即使两个perl
s 都使用行缓冲:(* 并且 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
,但这有点极端。)