tail
与其他标准工具结合使用分组命令可以建造一些强大的建筑。例如,要获取文件的第一行和最后一行:
$ seq 10 > file
$ { head -n1; tail -n1; } <file
1
10
当将文件内容从管道提供给组命令时,tail
无法生成输出,因为管道是联合国-查找有能力的:
$ seq 10 | { head -n1; tail -n1; }
1
现在,当内容足够大时,tail
就可以了:
$ seq 10000 | { head -n1; tail -n1; }
1
10000
那是因为在第一次lseek
失败之后,tail
知道这不是一次失败可搜索的文件描述符,并且由于管道的内容尚未全部读取,因此它开始读取内容直到结束。
从用户的角度来看,我希望无论输入内容大小如何,行为都应该保持一致。我浏览了 POSIX文档tail
,lseek
但没有找到任何描述。
这个行为是 POSIX 指定的吗?如果不是,我怎样才能使结果始终一致?
我已经用 GNU tail 和 FreeBSD tail 进行了测试,两者都有相同的行为。
答案1
请注意,问题不在于此处tail
,而是在于head
此处从管道读取的内容多于其要输出的第一行(因此没有任何内容可供tail
读取)。
是的,它符合 POSIX 标准。
head
当输入可查找时,需要将光标保留在 stdin 中,就在其输出的最后一行之后,但其他情况下则不然。
http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap01.html:
当标准实用程序读取可查找输入文件并在到达文件末尾之前无错误地终止时,该实用程序应确保打开的文件描述中的文件偏移量正确定位在该实用程序处理的最后一个字节之后。对于不可查找的文件,该文件的打开文件描述中的文件偏移量状态是未指定的。
因为head
能够对不可查找的文件执行此操作意味着它必须一次读取一个字节,这将是非常低效的。这就是read
或line
实用程序或 GNUsed
使用该-u
选项所做的事情。
因此,如果您想要这种行为,您可以替换head -n 20
为。gsed -u 20q
虽然在这里,你宁愿想要:
sed -e 1b -e '$b' -e d
反而。这里,只有一个工具调用,因此内部缓冲区不能在两个工具调用之间共享是没有问题的。但请注意,对于大文件,sed
读取整个文件的效率会较低,而对于可查找的文件tail
,则会跳过大部分文件寻求接近文件末尾。
请参阅有关缓冲的相关讨论为什么使用 shell 循环处理文本被认为是不好的做法?。
请注意,tail
必须在标准输入上输出流的尾部。虽然,作为一种优化和可查找文件,实现可能会查找文件末尾以从那里获取尾随数据,但不允许查找回到调用当时的初始位置之前的点tail
( Busyboxtail
曾经有过这个错误)。
例如:
{ cat; tail -n 1; } < file
尽管tail
可以返回到 的最后一行file
,但事实并非如此。它的标准输入是一个空流,cat
光标位于文件末尾;不允许通过在文件中进一步向后查找来从该流中回收数据。
(以上文字已划掉待定公开组的澄清并考虑到几个实现都没有正确完成)
1head
的内置函数(如果您放在前面ksh93
则启用),用于套接字(一种不可查找的文件)/opt/ast/bin
$PATH
偷看recvfrom(..., MSG_PEEK)
在实际输入之前(使用)阅读看看它需要阅读多少内容,以确保它不会阅读太多。对于其他类型的文件,则回退到一次读取一个字节。这稍微更有效率,我相信这是它用socketpair()
s 而不是 来实现管道的主要原因pipe()
。请注意,这并不完全万无一失,因为如果另一个进程从套接字之间读取数据,则可能会触发竞争条件。窥视和读。