头吃多余的字符


15

预期以下shell命令仅输出输入流的奇数行:

echo -e "aaa\nbbb\nccc\nddd\n" | (while true; do head -n 1; head -n 1 >/dev/null; done)

但是,它只是打印第一行:aaa

-c--bytes)选项一起使用时不会发生相同的事情:

echo 12345678901234567890 | (while true; do head -c 5; head -c 5 >/dev/null; done)

该命令1234512345按预期输出。但这仅在该实用程序的coreutils实现中有效head。该busybox的执行还是吃多余的字符,所以输出正好12345

我想这种特定的实现方式是出于优化目的而完成的。您不知道行的结尾,因此不知道需要读取多少个字符。不消耗输入流中多余字符的唯一方法是逐字节读取流。但是一次从流中读取一个字节可能很慢。因此,我想head将输入流读取到足够大的缓冲区中,然后计算该缓冲区中的行数。

--bytes使用option 时无法说相同的话。在这种情况下,您知道需要读取多少个字节。因此,您可以准确地读取此字节数,但不能超过此数目。该corelibs实现使用这个机会,但是busybox的一个没有,它仍然比读取所需到缓冲区的字节以上。这样做可能是为了简化实现。

所以这个问题。head实用程序从输入流中消耗比要求更多的字符是否正确?Unix实用程序是否有某种标准?如果存在,是否指定了这种行为?

聚苯乙烯

您必须按Ctrl+C停止上面的命令。Unix实用程序不会在超越时失败EOF。如果您不想按,则可以使用更复杂的命令:

echo 12345678901234567890 | (while true; do head -c 5; head -c 5 | [ `wc -c` -eq 0 ] && break >/dev/null; done)

我并没有为了简单起见。


2
Neardupe unix.stackexchange.com/questions/48777/...unix.stackexchange.com/questions/84011/...。另外,如果这个标题出现在movie.SX上,我的回答是Zardoz :)
dave_thompson_085

Answers:


30

head实用程序从输入流中消耗比要求更多的字符是否正确?

是的,它是允许的(请参阅下文)。

Unix实用程序是否有某种标准?

是的,POSIX第3卷,Shell&Utilities

如果存在,是否指定了这种行为?

它在介绍中确实如此:

当标准实用程序读取可搜索的输入文件并在到达文件结尾之前无错误终止时,该实用程序应确保打开的文件描述中的文件偏移量恰好位于该实用程序处理的最后一个字节之后。对于不可搜索的文件,未指定该文件的打开文件描述中的文件偏移状态。

head标准实用程序之一,因此,符合POSIX的实现必须实现上述行为。

GNU head 确实尝试将文件描述符保留在正确的位置,但是无法在管道上查找,因此在测试中它无法恢复该位置。您可以使用strace以下命令查看:

$ echo -e "aaa\nbbb\nccc\nddd\n" | strace head -n 1
...
read(0, "aaa\nbbb\nccc\nddd\n\n", 8192) = 17
lseek(0, -13, SEEK_CUR)                 = -1 ESPIPE (Illegal seek)
...

所述read返回17个字节(所有可用的输入),head处理这些四个然后尝试移回的13个字节,但它不能。(您还可以在这里看到GNU head使用8 KiB缓冲区。)

当您告诉head计数字节(非标准)时,它知道要读取多少个字节,因此可以(如果以这种方式实现)相应地限制其读取。这就是您的head -c 5测试起作用的原因:GNU head仅读取五个字节,因此不需要设法恢复文件描述符的位置。

如果将文档写到文件中,而改用该文件,则会得到以下行为:

$ echo -e "aaa\nbbb\nccc\nddd\n" > file
$ < file (while true; do head -n 1; head -n 1 >/dev/null; done)
aaa
ccc

2
可以使用line(现在已从POSIX / XPG中删除,但在许多系统上仍然可用)或readIFS= read -r line)实用程序代替,它们一次只能读取一个字节,以避免出现问题。
斯特凡Chazelas

3
请注意,head -c 5将读取5个字节还是读取一个完整的缓冲区取决于实现(也请注意这head -c不是标准的),您不能依靠它。您需要dd bs=1 count=5保证不会读取超过5个字节。
斯特凡Chazelas

感谢@Stéphane,我已经更新了-c 5说明。
史蒂芬·基特

请注意,当输入不可搜索时,head内置的一次ksh93读取一个字节head -n 1
斯特凡Chazelas

1
@anton_rh,dd只能用管道正常工作bs=1,如果您使用的是count管道上的读取可能会返回小于请求(但至少有一个字节,除非达到EOF)。GNU ddiflag=fullblock可以缓解,虽然。
斯特凡Chazelas

6

来自POSIX

工具应在其输入文件复制到标准输出,在指定点结束的每个文件的输出。

它没有说明head 必须从输入中读取多少内容。要求它逐字节读取将是很愚蠢的,因为在大多数情况下这将非常慢。

但是,这是在read内置/实用程序中解决的:我可以read从管道中一次找到一个字节一个字节的所有shell ,并且标准文本可以解释为必须执行此操作,才能仅读取一行:

实用程序应当从标准输入读取一个单一的逻辑线到一个或多个壳的变量。

readshell脚本中使用的情况下,一个常见的用例是这样的:

read someline
if something ; then 
    someprogram ...
fi

此处,的标准输入someprogram与shell 的标准输入相同,但是可以预期,它将someprogram读取由消耗的第一条输入行之后的所有内容,read而不是由缓冲的读取后剩下的所有内容read。另一方面,head在您的示例中使用as并不常见。


如果您确实要删除每隔一行,那么最好使用(可以更快地)使用一些可以一次性处理所有输入的工具,例如

$ seq 1 10 | sed -ne '1~2p'   # GNU sed
$ seq 1 10 | sed -e 'n;d'     # works in GNU sed and the BSD sed on macOS

$ seq 1 10 | awk 'NR % 2' 
$ seq 1 10 | perl -ne 'print if $. % 2'

但是请参阅第3卷POSIX简介的“输入文件”部分...
Stephen Kitt

1
POSIX说:“当标准实用程序读取可搜索的输入文件并在到达文件结尾之前无错误终止时,该实用程序应确保打开的文件描述中的文件偏移量恰好位于由处理的最后一个字节之后。该工具对于那些不可搜索的文件,该文件在打开文件描述抵消了该文件的状态是不确定的。
AlexP

2
请注意,除非您使用-r,否则read可能会读取多行(如果没有IFS=,则会删除前导和尾随空格和制表符(默认值为$IFS))。
斯特凡Chazelas

@AlexP,是的,斯蒂芬只是链接了这一部分。
ilkkachu

请注意,当输入不可搜索时,head内置的一次ksh93读取一个字节head -n 1
斯特凡Chazelas

1
awk '{if (NR%2) == 1) print;}'

Hellóka:-)并欢迎您在网站上!请注意,我们倾向于更详尽的答案。它们对于将来的Google员工应该很有用。
peterh-恢复莫妮卡
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.