(via https://stackoverflow.com/a/8624829/23582)
How does (head; tail) < file work? Note that cat file | (head;tail) doesn’t.
Also, why does (head; wc -l) < file give 0 for the output of wc?
Note: I understand how head and tail work. Just not the subtleties involved with these particular invocations.
OS X
For OS X, you can look at the source code for
headand the source code fortailto figure out some of what’s going on. In the case oftail, you’ll want to look atforward.c.So, it turns out that
headdoesn’t do anything special. It just reads its input using thestdiolibrary, so it reads a buffer at a time and might read too much. This meanscat file | (head; tail)won’t work for small files wherehead‘s buffering makes it read some (or all) of the last 10 lines.On the other hand,
tailchecks the type of its input file. If it’s a regular file,tailseeks to the end and reads backwards until it finds enough lines to emit. This is why(head; tail) < fileworks on any regular file, regardless of size.Linux
You could look at the source for
headandtailon Linux too, but it’s easier to just usestrace, like this:Take a look at
/tmp/head.trace. You’ll see that theheadcommand tries to fill a buffer (of 8192 bytes in my test) by reading from standard input (file descriptor 0). Depending on the size offile, it may or may not fill the buffer. Anyway, let’s assume that it reads 10 lines in that first read. Then, it useslseekto back up the file descriptor to the end of the 10th line, essentially “unreading” any extra bytes it read. This works because the file descriptor is open on a normal, seekable file. So(head; tail) < filewill work for any seekable file, but it won’t makecat file | (head; tail)work.On the other hand,
taildoes not (in my testing) seek to the end and read backwards, like it does on OS X. At least, it doesn’t read all the way back to the beginning of the file.Here’s my test. Create a small, 12-line input file:
Then, try
(head; tail) < /tmp/fileon Linux. I get this with GNU coreutils 5.97:But on OS X, I get this: