2

To be clear with the title question, I understand why the former dies. I don't understand why the latter doesn't, just for adding a | cat to the loop body.

Also maybe related,

while true; do echo y; done

dies immediately when I do ^C, but killing

while true; do echo y | cat; done

often takes hitting ^C more than once. Sometimes once works, other times 2 or 3 times works, then there are times where I need to hold ^C for a while for it to die.

Both behaviors happen in both bash and zsh, although the ^C one seems to be rarer with bash.

For both behaviors, this isn't limited to adding the pipe to cat. | dd, | tee, etc. also cause them. Even echo y | true causes it. It seems to be the presence of any pipe in the loop body.

Why does the presence of a pipe in a loop body change the loop's response to signals?

3

In while true; do echo y; done | true, since echo is builtin, you've a go a subshell process that write y\n in a loop to a pipe.

When true returns, the reading end of that pipe is closed, so writing to the writing end causes a SIGPIPE to be delivered to the writing process. Here, that's the subshell process that's running the loop.

In while true; do echo y | cat; done | true, it's cat which writes to the pipe. cat is generally not builtin, and even if it were, in shells other than zsh and ksh, all pipes component always run in child processes.

So, here only the process running cat dies and the subshell process that runs the loop carries on running more cat processes that die as soon as they write y\n on their stdout.

In ksh93/ksh2020, if you do:

$ builtin cat
$ type cat
cat is a shell builtin
$ set -o pipefail
$ while true; do echo y | cat; done | true; kill -l "$?"
PIPE

This time, cat is builtin and does run in the same process as the loop (as cat is the rightmost command in the first pipe line and ksh doesn't run that one in a subshell), so the subshell pipes to true does exit and kill -l confirms it was killed by a SIGPIPE.

1
  • Thanks! Everything makes a lot of sense now, and for the other case, after checking how ^C works, I guess ^C stopping the loop depends on whether an external program is currently running or not. In my example with the pipe, it only works if it's delivered in between iterations. The one without the pipe doesn't have external invocations, so it always works. It also explains why more conventional loops with external programs that last longer and have in-between shell code almost always take exactly 2 ^C in quick succession to kill the loop.
    – JoL
    Apr 15 '20 at 19:32

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.