How to pipe a bash command and keep Ctrl+C working?
Ctrl+C causes a SIGINT to be sent to all processes in the pipeline (as they're all run in the same process group that correspond to that foreground job of your interactive shell).
So in:
loopHelloWorld |
while IFS= read -r line; do
echo "$line"
done
Both the process running loopHelloWorld
and the one running the subshell that runs the while
loop will get the SIGINT
.
If loopHelloWorld
writes the Ctrl+C to nicely shutdown
message on its stdout, it will also be written to the pipe. If that's after the subshell at the other end has already died, then loopHelloWorld
will also receive a SIGPIPE, which you'd need to handle.
Here, you should write that message to stderr as it's not your command's normal output (doesn't apply to the ping
example though). Then it wouldn't go through the pipe.
Or you could have the subshell running the while loop ignore the SIGINT so it keeps reading the loopHelloWorld
output after the SIGINT:
loopHelloWorld | (
trap '' INT
while IFS= read -r line; do
printf '%s\n' "$line"
done
)
that would however cause the exit status of the pipeline to be 0 when you press Ctrl+C.
Another option for that specific example would be to use zsh
or ksh93
instead of bash
. In those shells, the while
loop would run in the main shell process, so would not be affected by SIGINT.
That wouldn't help for loopHelloWorld | cat
though where cat
and loopHelloWorld
run in the foreground process group.
Use a trap:
#! /bin/bash
trap handleInt SIGINT
interrupted=0
function handleInt {
echo "Interrupted..."
interrupted=1
}
while true
do
[[ interrupted -ne 0 ]] && { echo "Ctrl-C caught, exiting..." ; exit ; }
echo "Sleeping..."
sleep 1
read -r line
echo "$line"
done