Bash – Why does using `yes` on bash pipelines *not* cause infinite loops

bashpipeshell

According to its documentation, bash waits until all commands in a pipeline have finished running before continuing

The shell waits for all commands in the pipeline to terminate before returning a value.

So why does the command yes | true finish immediately? Shouldn't the yes loop forever and cause the pipeline to never return?


And a subquestion: according to the POSIX spec, shell pipelines may choose to either return after the last command finishes or wait until all the commands finish. Do common shells have different behavior in this sense? Are there any shells where yes | true will loop forever?

Best Answer

When true exits, the read side of the pipe is closed, but yes continues trying to write to the write side. This condition is called a "broken pipe", and it causes the kernel to send a SIGPIPE signal to yes. Since yes does nothing special about this signal, it will be killed. If it ignored the signal, its write call would fail with error code EPIPE. Programs that do that have to be prepared to notice EPIPE and stop writing, or they will go into an infinite loop.

If you do strace yes | true1 you can see the kernel preparing for both possibilities:

write(1, "y\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\n"..., 4096) = -1 EPIPE (Broken pipe)
--- SIGPIPE {si_signo=SIGPIPE, si_code=SI_USER, si_pid=17556, si_uid=1000} ---
+++ killed by SIGPIPE +++

strace is watching events via the debugger API, which first tells it about the system call returning with an error, and then about the signal. From yes's perspective, though, the signal happens first. (Technically, the signal is delivered after the kernel returns control to user space, but before any more machine instructions are executed, so the write "wrapper" function in the C library does not get a chance to set errno and return to the application.)


1 Sadly, strace is Linux-specific. Most modern Unixes have some command that does something similar, but it often has a different name, it probably doesn't decode syscall arguments as thoroughly, and sometimes it only works for root.

Related Question