我正在通过 ssh 执行以下脚本:
ssh user@host 'bash -s' < ./script.sh
问题是,有时,我得到的输出不正确,线条混杂。
在我的例子中,脚本执行了 notmuch new,正常输出如下:
...
Note: Ignoring non-mail file: foobar
Note: Ignoring non-mail file: foobar
Note: Ignoring non-mail file: foobar
Processed 93 total files in almost no time.
No new mail.
但有时输出如下:
...
Note: Ignoring non-mail file: foobar
Note: Ignoring non-mail file: foobar
Processed 93 total files in almost no time.
No new mail.
Note: Ignoring non-mail file: foobar
Note: Ignoring non-mail file: foobar
当然这不是真正的输出notmuch new
,命令以 结尾,No new mail
但它就像是通过 ssh 获取输出而不是逐行获取输出。
为什么会这样?
答案1
缓冲。如果我们在源代码中搜索notmuch
$ find . -name \*.c -exec grep 'Ignoring non-mail file' {} +
./notmuch-new.c: fprintf (stderr, "Note: Ignoring non-mail file: %s\n", filename);
$ find . -name \*.c -exec grep 'No new mail' {} +
./notmuch-new.c: printf ("No new mail.");
$
其中一些消息使用标准错误(默认情况下不缓冲),一些使用标准输出(默认情况下是行缓冲或块缓冲,取决于标准输出是到终端还是文件)。此行为来自标准 C 库,请参阅setvbuf(3)
了解详情。因此,stderr
消息会立即写入,而printf
对的调用stdout
将显示...嗯,这取决于情况。
缓冲通常由每个应用程序单独配置,但也许可以使用诸如stdbuf
(尽管有些人认为LD_PRELOAD
所使用的技巧stdbuf
非常可怕......)的实用程序来进行操作。
这种差异很容易在本地重现;例如写入终端(基于行的缓冲stdout
):
$ perl -E 'for (1..4) { say "out"; warn "err\n" }'
out
err
out
err
out
err
out
err
$
而如果将完全相同的代码重定向到文件(基于块的缓冲stdout
):
$ perl -E 'for (1..4) { say "out"; warn "err\n" }' >x 2>&1
$ cat x
err
err
err
err
out
out
out
out
$
ssh
增加了额外的复杂程度,因为人们可能还必须弄清楚它是如何收集、缓冲和发送字节的,以及notmuch
它ssh
在客户端系统上是如何连接的……