(通过https://stackoverflow.com/a/8624829/23582)
(head; tail) < file
如何运作?请注意,cat file | (head;tail)
没有。
另外,为什么(head; wc -l) < file
为0
的输出提供了wc
?
注意:我理解头部和尾部是如何工作的。只是不是这些特殊调用涉及的微妙之处。
答案 0 :(得分:17)
对于OS X,您可以查看source code for head
和source code for tail
以了解正在发生的一些事情。对于tail
,您需要查看forward.c
。
因此,事实证明head
没有做任何特别的事情。它只是使用stdio
库读取其输入,因此它一次读取一个缓冲区并且可能读取太多。这意味着cat file | (head; tail)
不适用于head
缓冲使其读取最后10行中的部分(或全部)的小文件。
另一方面,tail
检查其输入文件的类型。如果它是一个常规文件,tail
寻找到最后并向后读,直到找到足够的行发出。这就是(head; tail) < file
适用于任何常规文件的原因,无论大小如何。
您也可以在Linux上查看head
和tail
的来源,但更容易使用strace
,如下所示:
(strace -o /tmp/head.trace head; strace -o /tmp/tail.trace tail) < file
看看/tmp/head.trace
。您将看到head
命令尝试通过读取标准输入(文件描述符0)来填充缓冲区(在我的测试中为8192个字节)。根据{{1}}的大小,它可能会也可能不会填充缓冲区。无论如何,我们假设它在第一次读取时读取了10行。然后,它使用file
将文件描述符备份到第10行的末尾,基本上“读取”它读取的任何额外字节。这是有效的,因为文件描述符在普通的可搜索文件上打开。因此lseek
适用于任何可搜索文件,但不会使(head; tail) < file
正常工作。
另一方面,cat file | (head; tail)
不(在我的测试中)寻求结束并向后阅读,就像在OS X上一样。至少,它不会读取一直回到文件的开头。
这是我的测试。创建一个小的12行输入文件:
tail
然后,在Linux上尝试yes | head -12 | cat -n > /tmp/file
。我用GNU coreutils 5.97:
(head; tail) < /tmp/file
但是在OS X上,我明白了:
1 y
2 y
3 y
4 y
5 y
6 y
7 y
8 y
9 y
10 y
11 y
12 y
答案 1 :(得分:7)
这里的括号创建一个subshell
,它是运行内部命令的解释器的另一个实例,有趣的是子shell充当单个stdin / stdout组合;在这种情况下,它首先将stdin连接到head
,它回显前10行并关闭管道,然后子shell将其stdin连接到tail
,消耗其余的并将最后10行写回到stdout ,但子shell接受两个输出并将它们写为自己的标准输出,这就是它出现组合的原因。
值得一提的是,使用命令分组可以实现同样的效果,如{ head; tail; } < file
更便宜,因为它不会创建另一个bash实例。
答案 2 :(得分:3)
如果文件足够大,所有这些都应该按预期工作。 head命令将消耗一定量的输入(不仅仅是它缓冲它的输入所需的内容),如果没有为tail命令留下足够的输入,它将无法工作。
另一个问题是管道导致双方并行执行,因此生产方可能会导致消费方的head命令在每次运行时读取不同的数量。
比较以下命令的多次运行:
for i in `seq 1 10`; do echo "foo"; done | (head -n1; wc -l)
wc命令每次都应该看到不同数量的文件。
当使用<
提供输入时,似乎并不存在这种并行性(可能是bash读取整个输入然后将其传递给head命令)。
答案 3 :(得分:-2)
head命令显示前10行(默认)文件行。并且tail命令显示最后10行(默认)文件行。 假设文件只有3行也没问题,那些命令会显示这些行。 但是如果你有超过10行,那么这两个命令将只显示默认的10行。 将使用-n,n,+ n选项更改默认行数。 (参见手册页)