头部吃掉额外的字符

头部吃掉额外的字符

以下 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按预期输出。但这仅适用于核心工具该实用程序的实施head。这忙碌盒实现仍然会消耗额外的字符,因此输出只是12345.

我猜这种具体的实现方式是出于优化目的。你无法知道该行在哪里结束,因此你不知道需要读取多少个字符。不消耗输入流中额外字符的唯一方法是逐字节读取流。但是一次从流中读取一个字节可能会很慢。所以我想将head输入流读取到足够大的缓冲区,然后计算该缓冲区中的行数。

--bytes对于使用选项的情况则不能说同样的情况。在这种情况下,您知道需要读取多少字节。因此,您可以准确读取这个字节数,而不能超过这个字节数。这核心库实施利用了这个机会,但是忙碌盒如果没有,它仍然会在缓冲区中读取多于所需的字节。这样做可能是为了简化实现。

所以问题来了。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)

为了简单起见我没有使用它。

答案1

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

是的,这是允许的(见下文)。

Unix 实用程序有某种标准吗?

是的,POSIX 第 3 卷,Shell 与实用程序

如果有的话,它是否指定了这种行为?

在其简介中确实如此:

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

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

GNUhead 尝试将文件描述符保留在正确的位置,但不可能在管道上查找,因此在您的测试中它无法恢复该位置。您可以使用以下命令查看此内容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)
...

返回read17 个字节(所有可用输入),head处理其中的 4 个字节,然后尝试移回 13 个字节,但它不能。 (您还可以在此处看到 GNUhead使用 8 KiB 缓冲区。)

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

如果您将文档写入文件并使用该文件,您将得到您想要的行为:

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

答案2

来自 POSIX

实用程序应将其输入文件复制到标准输出,并在指定点结束每个文件的输出。

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

然而,这是在read内置/实用程序中解决的:我可以read从管道中一次找到一个字节的所有外壳标准文本可以解释为必须这样做才能读出那一行:

实用程序应将标准输入中的单个逻辑行读取到一个或多个 shell 变量中。

对于readshell 脚本中使用的 ,常见用例如下:

read someline
if something ; then 
    someprogram ...
fi

这里, 的标准输入someprogram与 shell 的标准输入相同,但可以预期 getssomeprogram会读取 被 消耗的第一个输入行之后的所有内容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

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

相关内容