Consider the following script:
#!/bin/bash
set -o pipefail
set -o history
trapper() {
func="$1" ; shift
for sig ; do
trap "$func $sig" "$sig"
done
}
err_handler () {
case $2 in
INT)
stop_received=1
;;
TSTP)
;;
ERR)
if [[ $2 != "INT" ]]; then # for some reason, ERR gets triggered on SIGINT
code=$?
if [ $code -ne 0 ]; then
echo "Failed on line $1"
echo "$BASH_COMMAND returned $?"
echo "Content of variables at the time of failure:"
echo "$(set -o posix; set)"
exit 1
fi
fi
;;
esac
}
main() {
ping -c 5 www.google.com # this is a test to see if INT interrupts main()
# do a bunch of stuff, I mean a real bunch of stuff, like check some
# files, do some processing, connect to a database,
# do more processing, move some files around, you get the drift
}
exec > >(tee -a my.log)
exec 2>&1
trapper 'err_handler $LINENO' INT TSTP ERR
while main
do
if [[ "$stop_received" == "1" ]]; then
break
fi
setsid sleep 2 & wait
done
trap ERR
What I am trying to accomplish is to run the script in an infinite loop until either the function main() returns some non zero value, i.e. some error occurred, or SIGINT is received.
However, I don't want SIGINT to stop main() from executing, in other words, if the script receives a SIGINT, it should wait for main() to finish, then exit nicely. But when I hit CTRL+C, I can see that ping is interrupted. At the moment I commented out everything under main() just to see if this works. Since ping gets interrupted, I am assuming other commands under main() would also get interrupted. When ping is interrupted, the processing jumps to the line where I check if $stop_received=1 and then the loop breaks and the script quits. If I replace the break with just an echo, then the script just continues on to the next iteration of the while main loop.
How can I stop SIGINT from interrupting the currently running command(s)? Since my script does a bunch of stuff, including DML statements in a database, interrupting main() would cause lot of grief.
Secondly, the script does not trap ctrl+z either. Or rather, the script just gets stuck on ctrl+z requiring a kill pid to terminate. I assumed that sleep being a child of bash as opposed to the script itself, ctrl+z would cause the script to pause, leaving sleep in limbo land. Hence the setsid and wait on sleep, but it still hangs.
thanks.
Best Answer
There are several ways you can cut off the effect of Ctrl+C:
Since you want to detect that Ctrl+C has been pressed, ignoring the signal is out. You could change the terminal settings, but then you would need to write custom key processing code. Shells don't provide access to signal blocking.
You can however isolate subprocesses from receiving the signal automatically by running them in a separate process group. Interactive shells run background commands in a separate process group by default, but non-interactive shells run them in the same process group, and all processes in the foreground process group receive a signal from terminal events. To tell the shell to run background jobs in a separate process group, run
set -m
. Runningsetsid ping …
is another way of forcingping
to run in a separate process group.If you want Ctrl+Z to suspend a background process group, you'll need to propagate the signal from the shell.
Controlling signals finely is a bit of a stretch for a shell script, and shells other than ATT ksh tend to be a little buggy when you reach the corner cases, so consider a language that gives you more control such as Perl, Python or Ruby.