这是 POSIX 指定的分组命令中的尾部行为吗?

cuo*_*glm 9 pipe tail posix

使用tail与其他标准工具组合分组命令可以使一些强大的结构。例如,要获取文件的第一行和最后一行:

$ seq 10 > file
$ { head -n1; tail -n1; } <file
1
10
Run Code Online (Sandbox Code Playgroud)

当从管道组命令馈送文件的内容,tail不能产生输出,因为管是lseek的能够

$ seq 10 | { head -n1; tail -n1; }
1
Run Code Online (Sandbox Code Playgroud)

现在,当内容足够大时,tail工作:

$ seq 10000 | { head -n1; tail -n1; }
1
10000
Run Code Online (Sandbox Code Playgroud)

那是因为在第一次lseek失败后,tail知道它不是一个可查找的文件描述符,并且因为管道的内容还没有被全部读取,它开始读取内容直到结束。

作为用户的观点,我希望无论输入内容大小如何,行为都应该是一致的。我查看了 POSIX taillseek文档并没有找到任何描述。

这种行为是否由 POSIX 指定?如果没有,我怎样才能使结果始终一致?


我已经用 GNU tail 和 FreeBSD tail 进行了测试,两者都具有相同的行为。

Sté*_*las 8

请注意,问题不在于tailhead这里从管道中读取的内容多于它要输出的第一行(因此没有任何内容可供tail读取)。

是的,它符合 POSIX。

head 当输入可搜索时,需要将光标留在 stdin 中,就在它输出的最后一行之后,否则不行。

http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap01.html

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

因为head能够对不可查找的文件执行此操作将意味着它必须一次读取一个字节,这将非常低效¹。这就是readorline实用程序或 GNUsed-u选项。

所以,你可以替换head -n 20使用gsed -u 20q,如果你想要的行为。

虽然在这里,你宁愿想要:

sed -e 1b -e '$b' -e d
Run Code Online (Sandbox Code Playgroud)

反而。在这里,只有一个工具调用,因此不能在两个工具调用之间共享的内部缓冲区没有问题。但是请注意,对于大文件,sed读取整个文件时效率会降低,而对于可查找文件,tail会通过在文件末尾附近查找来跳过大部分内容。

请参阅为什么使用 shell 循环来处理被认为是不好的做法的文本中有关缓冲的相关讨论.

请注意,tail必须在标准输入上输出流的尾部。虽然作为优化和可查找文件,实现可能会寻找文件末尾以从那里获取尾随数据,但不允许返回到tail调用时初始位置之前的点( Busyboxtail曾经有那个错误)。

所以例如在:

{ cat; tail -n 1; } < file
Run Code Online (Sandbox Code Playgroud)

尽管tail可以返回到 的最后一行file,但它没有。它的 stdin 是一个空流,就像cat文件末尾的光标一样;不允许通过在文件中进一步向后查找来从该流中回收数据。

(上面的文字被划掉了,等待开放组的澄清,并考虑到它没有被几个实现正确完成)


¹本head的内置ksh93(启用如果你把/opt/ast/bin前面的$PATH),对于插座(一种非可搜索的文件),而不是偷窥在输入(使用recvfrom(..., MSG_PEEK))来实际前阅读它,看它需要多少阅读,以确保它不不要读太多。并回退到其他类型的文件一次读取一个字节。这稍微更有效率,我相信这是它用socketpair()s 而不是pipe(). 请注意,这并不完全是万无一失的,因为如果另一个进程从peekread之间的套接字读取,则可能会触发竞争条件。