Ankur Agarwal
Ankur Agarwal

Reputation: 24768

Unix/Linux pipe behavior when reading process terminates before writing process

I have this:

$ ls -lh file
-rw-r--r-- 1 ankur root 181M Sep 23 20:09 file

$ head -6 file

z
abc
abc
abc
abc
abc

$ cat file | grep -m 1 z
z

Question:

Why is the cat command line in the last line not dying prematurely with SIGPIPE? I think this should happen because grep terminates in no time compared to cat file that cats 183MB of file. With reading process gone cat will try to write to a broken pipe and should die with SIGPIPE.

Update:

I ended up writing this: readstdin.c

# include <unistd.h>
# include <stdio.h>

int main() {

    ssize_t n ;        
    char a[5];        
    n = read(0, a, 3);
    printf("read %zd bytes\n", n);
    return(0);

}

I use it like this:

$ cat file | ./readstdin
$ yes | ./readstdin

But still cat or yes does not die prematurely. I expect it to because by reading process is terminating before writing process is done writing.

Upvotes: 0

Views: 1996

Answers (2)

If the read end of some pipe(2) is close(2)-ed, further write(2)s will get a SIGPIPE signal(7). Read also pipe(7).

They would get the SIGPIPE when the pipe buffer becomes full.

In the yes | ./readstdin command, the yes command gets a a SIGPIPE signal. Just try yes in a terminal, it spits some output indefinitely ad nauseam till you kill it.

In the cat file | ./readstdin command, it could happen (notably if file is quite small, less that sysconf(_POSIX_PIPE_BUF) bytes, which might be 4096 bytes), that the cat command is close(2)-ing the STDOUT_FILENO descriptor and that the pipe is still not full. Then cat may not get any SIGPIPE.

Upvotes: 2

BraveNewCurrency
BraveNewCurrency

Reputation: 13065

Normal processes close the input stream causing a SIGPIPE. In the man page, it mentions that -m stops reading, and "ensures that standard input is positioned to just after the last matching line before exiting". So it doesn't actually close the stream. You can demonstrate like this:

cat file | (grep -m1 z && grep -m1 c)

You'll get the first c after the first z, which is sometimes useful. After the last grep exits, there is no place for the stream to go, so it's left unread and the whole group of commands exits. You can demonstrate:

(while true; do echo z; sleep 1; done) | grep -m3 z
(while true; do echo z; sleep 1; done) | grep --line-buffered z | head -3

Upvotes: 1

Related Questions