For head -n 10 to be able to read 10 lines and not a single more character from the pipe on stdin, it would have to read one character at a time to be not read anything after the last newline character. That would be inefficient.
That's what the read shell builtin does when stdin is not seekable.
{
head -n 10 > /dev/null
cat
} < myfile
Works because head reads a chunk of data and lseeks back to just after the end of the 10th line. That obviously can't be done with pipes.
On recent GNU or FreeBSD systems, some applications that use stdio can be told to read their input one character at a time by using stdbuf -i1 or stdbuf -i0.
However, that doesn't work with GNU head. It works with GNU sed though, so you could do:
seq 20 | {
stdbuf -i0 sed -n 10q
cat
}
Alternatively, what you could do is control what goes on a pipe so that there's ever at most only one line in it at a time.
For instance, on Linux, you could do:
one_line_at_a_time() {
perl -MTime::HiRes=usleep -pe '
BEGIN{$|=1;open F, "<", "/dev/fd/1"; $r=""; vec($r,fileno(F),1) = 1}
usleep(1000) while select($ro=$r,undef,undef,0)'
}
seq 20 | one_line_at_a_time | { head -n 10 > /dev/null; cat; }
That perl script opens "/dev/fd/1" in read mode which on Linux causes the other end of the pipe connected to fd 1 (stdout) to be opened. That way, with select, it can check if there's something in the pipe (and sleep until it's been emptied) before sending the next line.
Of course, that's also terribly inefficient.
{ }.catin each script to flushSTDINand pass stream toSTDOUT.ungetc()?