stdin
is file descriptor 0
. Closing a file descriptor of a process is something that can only be done actively by the process itself. stdin is closed when the process decides to close it period.
Now, when the stdin of a process is the reading end of a pipe, the other end of the pipe can be open by one or more other processes. When all the file descriptors to the other end have been closed, reading from that pipe will read the remaining data still in that pipe, but will then end up returning nothing (instead of waiting for more data) meaning end-of-file.
Applications like cat
, cut
, wc
... that read from their stdin will usually exit when that happens because their role is to process their input till the end until there's no more input.
There's no magic mechanism that causes applications to die when the end of their input is reached, only them deciding to exit when that happens.
In:
echo foo | cat
Once echo
has written "foo\n"
, it exits which causes the writing end of the pipe to be closed, then the read()
done by cat
at the other end returns 0 bytes, which tells cat
there's nothing more to read and then cat
decides to exit.
In
echo foo | sleep 1
sleep
only exits after 1 second has elapsed. Its stdin becoming a closed pipe has no incidence on that, sleep
is not even reading from its stdin.
It's different on the writing end of pipes (or sockets for that matters).
When all the fds on the reading end have been closed, any attempt to write on the fds open to the writing end causes a SIGPIPE to be sent to the process causing it to die (unless it ignores the signal in which case the write()
fails with EPIPE
).
But that only happens when they try to write.
For instance, in:
sleep 1 | true
Even though true
exits straight away and the reading end is then closed straight away, sleep
is not killed because it doesn't attempt to write to its stdout.
Now, about /proc/fd/pid/n
showing in red in the ls -l --color
output (as mentioned in the first version of your question), that's only because ls
does a lstat()
on the result of readlink()
on that symlink to try and determine the type of the target of the link.
For file descriptors opened on pipes, or sockets or files in other namespaces, or deleted files, the result of readlink
will not be an actual path on the file system, so the second lstat()
done by ls
will fail and ls
will think it's a broken symlink, and broken symlinks are rendered in red. You'll get that with any fd to any end of any pipe, whether the other end of the pipe is closed or not. Try with ls --color=always -l /proc/self/fd | cat
for instance.
To determine whether a fd points to a broken pipe, on Linux, you can try lsof
with the -E
option.
$ exec 3> >(:) 4> >(sleep 999)
$ lsof -ad3-4 -Ep "$$"
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
zsh 13155 stephane 3w FIFO 0,10 0t0 5322414 pipe
zsh 13155 stephane 4w FIFO 0,10 0t0 5323312 pipe 392,sleep,0r
For the fd 3, lsof was not able to find any other process at the reading end of the pipe. Beware though, that you could get output like:
$ exec 5<&3
$ lsof -ad3-5 -Ep "$$"
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
zsh 13155 stephane 3w FIFO 0,10 0t0 5322414 pipe 13155,zsh,5w
zsh 13155 stephane 4w FIFO 0,10 0t0 5323312 pipe 392,sleep,0r
zsh 13155 stephane 5w FIFO 0,10 0t0 5322414 pipe 392,sleep,3w 13155,zsh,3w
fds 3 and 5 are still to broken pipes, because there's no fd to the reading end (there seems to be a bug in lsof, since the fact that sleep
also has its fd 3 open to the broken pipe is not reflected everywhere).
To kill a process as soon as the pipe open on its stdin loses its last writer (becomes broken), you could do something like:
run_under_watch() {
perl -MIO::Poll -e '
if ($pid = fork) {
$SIG{CHLD} = sub {
wait;
exit($? & 127 ? ($? & 127) + 128 : $? >> 8);
};
$p = IO::Poll->new; $p->mask(STDIN, POLLERR); $p->poll;
kill "TERM", $pid;
sleep 1;
kill "KILL", $pid;
exit(1);
} else {
exec @ARGV
}' "$@"
}
Which would watch for an error condition on stdin (and on Linux, that seems to happen as soon as there's no writer left, even if there's data left in the pipe) and kill the child command as soon as it happens. For instance:
sleep 1 | run_under_watch sleep 2
Would kill the sleep 2
process after 1 second.
Now, generally that's a bit of a silly thing to do. That means you're potentially killing a command before it has had time to process the end of its input. For instance, in:
echo test | run_under_watch cat
You'll find that cat
is sometimes killed before it has had time to output (or even to read!) "test\n"
. There's no way around that, our watcher can't know how much time the command needs to process the input. All we can do is give a grace period before the kill "TERM"
hoping it's enough for the command to read the content left in the pipe and do what it needs to do with it.
1
is stdout, not stdin. – Stéphane Chazelas Feb 24 '16 at 10:47red
for every pipe. Becausels
tries to do alstat
on the result ofreadlink
, but that string returned byreadlink
is not a real path on the file system for pipes or any other fd that doesn't point to a file (also include sockets). There's no way to detect a broken via stat()/lstat() on /proc/pid/fd/*. – Stéphane Chazelas Feb 24 '16 at 10:51yes
on bash pipelines not cause infinite loops? – muru Feb 24 '16 at 11:23/dev/pts/2
, then its stdin is NOT a pipe, it's a terminal window. – zwol Feb 24 '16 at 17:36