【问题标题】:Why does the first word get lost?为什么第一个单词会丢失?
【发布时间】:2016-08-10 22:42:10
【问题描述】:

运行以下命令给出了意想不到的(对我而言)结果。

(echo -n foo; sleep 4; echo bar) | while :; do read -t2 r; echo "###$r@@@"; done
###@@@
###@@@
###bar@@@
###@@@
...

我想知道“foo”去了哪里。我希望它会被第一个 read 调用作为部分行读取。我希望得到如下内容:

(echo -n foo; sleep 4; echo bar) | while :; do read -t2 r; echo "###$r@@@"; done
###foo@@@
###@@@
###bar@@@
###@@@
...

因为显然这种行为(或它的文档)“最近”发生了变化,所以这里是我的 bash 版本。

bash --version
GNU bash, version 4.3.42(1)-release (x86_64-unknown-linux-gnu)

【问题讨论】:

  • 什么是$BASH_VERSION
  • @Barmar 已添加到问题中。
  • 4.4 中的相同行为,(echo -n foo; sleep 3) | (read -t r || echo "timeout: =$r=") 无法输出应该是部分读取的内容。这可能是缓冲问题和/或错误。
  • @chepner,是的。在阅读了一些回复后,我开始这么认为。也许我会打开一个错误报告,最坏的情况是 bash 开发人员告诉我我做错了什么。

标签: bash pipe


【解决方案1】:

-t 选项的文档说:

如果在 timeout 秒内没有读取完整的输入行,则导致 read 超时并返回失败。

由于 foo 回显后没有换行,前两个 reads 无法读取完整的输入行,因此它们超时并返回失败。它不会将部分输入分配给$r,而只是将其丢弃。

第三个read 成功,因为bar 后面跟着一个换行符,所以它被读作一个完整的行。

以上来自bash 4.3 之前的版本。你写的似乎应该在 4.3+ 中工作,我不确定为什么它仍然像旧版本一样。

【讨论】:

  • 真的吗?我的 bash 手册页显示“如果读取超时,读取会将读取的任何部分输入保存到指定的变量名中。”
  • 我在 www.gnu.org 的 bash 4.3 手册中看到了这一点。但是我在我的 Debian 服务器上的 4.2.37 或我的 OS X 机器上的 3.2.48 的手册页中没有看到它。
  • 不确定您使用的是什么 bash,但我看到“-t 如果在超时秒内未读取完整的输入行,则会导致读取超时并返回失败。”linux.die.net/man/1/bash
  • 4.4 的手册页确实表明 read 将在发生超时时将部分读取输入分配给其参数。
【解决方案2】:

让我们分析你的命令:

回声

echo部分,简单的输出foobar。
只是碰巧 foo 的退出时间与 bar 不同:

$ (echo -n foo; sleep 1; echo bar)
foobar
$ (echo -n foo; sleep 1; echo bar) | cat
foobar

阅读

读取部分只是在变量中获取该输出:

$ (echo -n foo; sleep 1; echo bar) | { read -t2 r; echo "###$r@@@"; }
###foobar@@@

如果睡眠时间大于读取超时时间,则读取一无所获:

$ (echo -n foo; sleep 4; echo bar) | { read -t2 r; echo "###$r@@@"; }
###@@@

虽然(多行)。

一段时间后,重复读取。一些读取获得所有输入,然后,在管道关闭后,读取立即获得 EOF(发生这种情况时它不会等待超时)。发生这种情况时它会返回一个错误,但没有检查 read 是否有错误,因此循环重复。

所以:创建了许多行:

$ (echo -n foo; sleep 1; echo bar) |
{ while :; do read -t2 r; echo "###$r@@@"; done; }

我们可以将输出重定向到一个文件(testfile.txt),而且,我们可以控制它的工作时间(为了便于阅读,分成三行)(> 不是命令的一部分):

$ { echo -n foo; sleep 1; echo bar; } | { 
> while :; do read -t2 r; echo "###$r@@@"; \
> done; } > testfile.txt & sleep 2; kill $!
[1] 28541
[1]+  Terminated  ....

现在我们可以看到写了多少行:

$ wc -l testfile.txt
35543 testfile.txt

这意味着每秒大约有 35k 行写入文件。
最后一次睡眠后(杀死前)的数字必须大于第一次睡眠时的数字,否则不会将任何行写入文件。

哪几行:

其中哪些不是重复###@@@

$ grep -vn "^###@@@"
1:###foobar@@@

所以,只有文件的第一行实际包含信息,其余的是###@@@的连续重复。

睡眠少于阅读-t

如果睡眠时间 (1) 低于读取超时 (2),就会发生这种情况。 1 表示睡眠,2 表示阅读。

睡眠时间比阅读时间长-t

如果我们让休眠时间长于读取超时时间:

$ a=3; b=2; c=5
$ { echo -n foo; sleep $a; echo bar; } | { 
> while :; do read -t$b r; echo "###$r@@@"; \
> done; } > testfile.txt & sleep $c; kill $!
[1] 29032
[1]+  Terminated ........

$ wc -l testfile.txt
65226 testfile.txt                      ### Again ~ 30 k per second.
$ grep -vn "^###@@@" testfile.txt
2:###bar@@@

结论?

似乎正在发生的事情是,在回显写入其输出之后,while 会不断重复读取接收“空”输入,并将其写入文件(尽可能多的 ###@@@ 行)。

只有当第一个回显睡眠时间长于第一个读取超时 (b=2) 时 (a=3),我们才会在 foobar 两个不同的行中得到一个划分。

这似乎是说foo 在时间 0 熄灭,第一次读取超时而没有收到完整的行,并发出初始 ###@@@(吃掉 foo 而没有输出?)。然后bar 带有第二次读取的结束换行符,此读取发出###bar@@@。由于管道的左侧已终止,因此 read 不断产生空行。

如果睡眠时间为 4,超时时间为 2,(根据您的问题)超时可能会发生两次,并且可能会在输出中写入两行。这将使bar 成为第三行(两次超时,在小节线旁边),但这并不能保证,因为两个睡眠控制着这一点,它们可能会徘徊一点。

在管道左侧的所有输出都已发出之后。读取接收到许多“空行”???。哪些被写入文件,我们可以分析它们。

一些松散的问题。

静态(暂时)读取。

我们可以做一个更具体的命令来分析:

$ { echo 'foo'; sleep 3; echo 'bar'; } |
  { read -t1 r; echo "read r value=|$r|"; \
    read -t1 s; echo "read s value=|$s|"; }
read r value=|foo|
read s value=||

如果睡眠 (3) 比读取时间长,则仅读取第一个 foo

$ { echo 'foo'; sleep 1; echo 'bar'; } |
  { read -t2 r; echo "read r value=|$r|"; \
    read -t2 s; echo "read s value=|$s|"; }
read r value=|foo|
read s value=|bar|

如果输入出现的读取超时时间较长,foobar 都会被读取。

虽然

如果我们包含一个简单的 while:

{ echo -n foo; sleep 4; echo bar; } | {
    while read -t2 r; do
    echo "###$r@@@";
done;
}

仅当睡眠低于读取超时时,我们才不会得到输出:完整的 foobar。 如果睡眠时间长于读取超时:第一次读取失败,退出代码为 1,并且退出 while。

这就是为什么我们会获得无限的时间。但这需要一个文件和一个最终超时,在代码中:

a=3; b=2; c=5
{ echo -n foo; sleep $a; echo bar; } | {
    while :; do
    read -t$b r;
    echo "###$r@@@";
done;
} > testfile.txt & sleep $c; kill $!

wc -l testfile.txt; grep -vn "^###@@@" testfile.txt

【讨论】:

  • 无限while的原因是因为在第二个echo之后管道关闭了,所以read立即得到EOF(发生这种情况时它不会等待超时)。发生这种情况时它会返回一个错误,但不会检查read 是否有错误,因此循环重复。
  • 基本问题是无法区分超时和其他错误。
  • 感谢@Barmar 尝试将您的两个 cmets 都包含在内(但请留下它们),它们现在很有用。
【解决方案3】:

如果您取出-t 2,您将看到它捕获foobar。这是否给了你足够的提示?

您已选择在行尾回显“foo”而不返回,但read 正在等待 CR 触发它。 foo 不会发生这种情况,因此 read 只需超时两次等待获取任何内容。然后,当回显 'bar\n' 时,它确实包含一个 CR,因此 read 将其拾取。

更新 我一直在查看旧版本 bash 的文档。最初的问题已更新为包含 bash 版本。如前所述,似乎 bash 文档已更改,但实际行为并未更改,并且可能涉及 bash 错误。

4.3 之前的文档表明,在超时时会引发错误,但没有表明是否将任何缓冲输入读入下一个变量。 4.3 文档说“如果读取超时,读取将读取的任何部分输入保存到指定的变量名称中。”

根据这个新信息,我在这个线程上的许多 cmets 都是不正确的。

【讨论】:

  • 但问题是为什么read 的下一个电话不接“foo”。它似乎只是迷失在某处的虚空中。此外,我希望第一次调用能够接听它,因为它应该在超时结束之前写入。
  • 是的,它只是被丢弃了。正如我在评论中所说,如果您取出“-t 2”并且不强制它超时并取走它所拥有的(什么都没有),那么您将不会丢失“foo”。或者如果你用'-n 3'替换它,只是因为。以交互方式使用它,将您的 (...) 替换为 cat | 并在终端上输入内容。
  • 我没有遵循你的逻辑。如果它什么都没有,它怎么能丢弃“foo”?如果它读过“foo”,它就什么也没有了。
  • 它还没有读取 foo。它在发送 CR 并且 foo 丢失之前超时。
  • 在读取换行符之前(我认为您的意思是 CR)或在读取任何输入之前?
【解决方案4】:

试试

... | while read a
do
    ...
done

我想可能是虚拟命令 ':' 接受输入。

【讨论】:

  • : 命令不读取任何输入。你只是在编造东西。
  • 这可能是:p,我发现不使用 read 作为循环命令来处理错误很奇怪
  • 如果使用read作为循环命令,则在读取超时时循环停止。
  • 您不能只阅读: 命令的文档以查看它是否读取任何输入吗?很清楚,它说除了在其参数中扩展变量之外什么都不做。
  • @quazardous 稍加研究即可轻松消除的错误猜测只是噪音。
猜你喜欢
  • 1970-01-01
  • 2013-11-19
  • 2020-01-19
  • 1970-01-01
  • 2016-12-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多