通过 ssh 执行脚本,而不是真正的输出

res*_*es1 2 linux ssh bash shell-script

我以这种方式通过 ssh 执行脚本:

 ssh user@host 'bash -s' < ./script.sh
Run Code Online (Sandbox Code Playgroud)

问题是,有时,我得到的输出不正确,行是混合的。

在我的情况下,脚本执行不是很新,正常输出是这样的:

...
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.
Run Code Online (Sandbox Code Playgroud)

但有时输出是这样的:

...
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
Run Code Online (Sandbox Code Playgroud)

并且可以肯定这不是来自 的真正输出notmuch new,该命令以 结尾,No new mail但它就像是通过 ssh 而不是逐行获取输出。

为什么会发生这种情况?

thr*_*rig 5

缓冲。如果我们搜索源代码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.");
$ 
Run Code Online (Sandbox Code Playgroud)

其中一些消息使用标准错误(默认情况下是无缓冲的),有些使用标准输出(默认情况下是行缓冲或块缓冲,取决于标准输出是发送到终端还是发送到文件)。此行为来自标准 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
$ 
Run Code Online (Sandbox Code Playgroud)

而相反,如果完全相同的代码被重定向到一个文件(基于块的缓冲stdout):

$ perl -E 'for (1..4) { say "out"; warn "err\n" }' >x 2>&1
$ cat x
err
err
err
err
out
out
out
out
$ 
Run Code Online (Sandbox Code Playgroud)

ssh增加了一层额外的复杂性,因为人们可能还必须弄清楚它是如何收集、缓冲和发送字节的,notmuch以及然后ssh连接到客户端系统上的内容......