'while head -n 1' 好奇心

问题描述 投票:0回答:2

一些编码实验,(在试图找到编码问题的较短答案时进行),导致了一些有趣的惊喜:

seq 2 | while head -n 1 ; do : ; done

输出(点击 Control-C 否则它会永远浪费 CPU 周期):

1
^C

相同,但使用重定向输入文件而不是管道输入

seq 2 > two
while head -n 1 ; do : ; done < two

输出(点击Control-C):

1
2
^C

问题:

  1. 为什么

    while
    循环不会像
    seq 2 | head -n 1
    那样停止?

  2. 为什么重定向输入管道输入产生更多输出?


以上代码在最近的

Lubuntu
上用
dash
bash 进行了测试。
seq
head
都来自coreutils(版本8.25-2ubuntu2)包。

绕过必须按(Ctrl-C)的方法:

timeout .1 sh -c "seq 2 > two ; while head -n 1 ; do : ; done < two"

1
2

timeout .1 sh -c "seq 2 | while head -n 1 ; do : ; done"

1

bash while-loop posix sh unix-head
2个回答
3
投票

head -n 1
,当在 stdin 上给出一个空流时,完全符合其权利和规范,可以立即以成功退出状态退出。

因此:

seq 2 | while head -n 1 ; do : ; done

...可以合法地永远循环,因为

head -n 1
不需要以非零状态退出并因此终止循环。 (只有在“发生错误”时,标准才需要非零退出状态,并且文件行数少于输出请求的行数不被定义为错误)。

确实,这是明确的:

当一个文件包含的行少于number行时,应将其完整复制到标准输出。这应该不是一个错误。


现在,如果你的

head
实现,在它第一次调用之后,(打印第一行的内容),退出时让文件指针在第二行的开头排队,(这是绝对不需要这样做),那么第二个循环实例将读取第二行并发出它。然而,这又是一个实现细节,它取决于编写您的
head
实现的人是否选择了 either:

  1. 读取一个非常大的块,但只发出它的一个子集。 (更有效的实施。)
  2. 逐个字符循环以仅消耗一行。

实施者完全有权根据仅在运行时可用的标准来决定要遵循哪些实施。


现在,假设您的

head
always 尝试一次读取 8kb 块。那么,它怎么能让指针在第二行排队呢? [* - 除了向后查找,一些实现在给定文件时执行,但标准不需要;感谢 Rob Mayhoff 在这里的指点]

这可能会发生如果

seq
的并发调用在第一个
read
发生时只写入并刷新了一行
.

显然,这是一个对时间非常敏感的情况——竞争条件——并且还取决于未指定的实现细节,(

seq
是否在行之间刷新其输出——因为
seq
未指定为 POSIX 的一部分或任何其他标准,在平台之间是完全不同的)。


0
投票

接受的答案是正确的。

head
不会为输入返回非零值(即使没有输入)

但我确实发现了一些好奇心


我想出了一个正确停止的方法。

seq 10 | while head -c 4 | ifne -n false; do : ; done;

可悲的是,由于

head
的输出遍历
while
.

的主体,因此您无法对构造做太多事情

我发现的一个用途是每 x 个字节插入一个字符。 (包括尾巴)

/> printf '12345678910' | { while head -c 2 | ifne -n false; do printf 'a'; done; }
/> 12a34a56a78a91a0a

你应该改用

sed 's/.\{4\}/&a/g'

这里有一个稍微有用一点的,它需要 2 个字节的输入“procees”,然后把它放在某个地方:

printf '12345678910' | { while true; do head -c 2 < /dev/stdin | ifne -n false >> file.txt || break; done;

你应该改用

split --filter

另一个非常奇怪的用例,当您尝试在 while 循环中使用

head
调用
/dev/stdin

/> printf '12345678910' | { while head -c 2 | ifne -n false; do head -c 3 </dev/stdin | ifne -n false >> every3.txt || break; done > every2.txt; }
/> cat every2.txt
12670
/> cat every3.txt
345891

如您所见,每 2 个字节循环一次,然后每 3 个字节循环一次。

12  345  67  891  0

你可能应该使用

bbe
代替

你可以将它用作某种穷人进度指示器

/> printf '12345678910' | while head -c 2 | ifne -n false; do echo "2 bytes travelled" > /dev/stderr ; done > /dev/null;
2 bytes travelled
2 bytes travelled
2 bytes travelled
2 bytes travelled
2 bytes travelled
2 bytes travelled # imperfect because actually only 1 byte travelled here

你可能应该使用

pv
代替


你能实际上用这个结构做什么......

¯\_(ツ)_/¯

© www.soinside.com 2019 - 2024. All rights reserved.