Forward SIGTERM to child in Bash
Try:
#!/bin/bash
_term() {
echo "Caught SIGTERM signal!"
kill -TERM "$child" 2>/dev/null
}
trap _term SIGTERM
echo "Doing some initial work...";
/bin/start/main/server --nodaemon &
child=$!
wait "$child"
Normally, bash
will ignore any signals while a child process is executing. Starting the server with &
will background it into the shell's job control system, with $!
holding the server's PID (to be used with wait
and kill
). Calling wait
will then wait for the job with the specified PID (the server) to finish, or for any signals to be fired.
When the shell receives SIGTERM
(or the server exits independently), the wait
call will return (exiting with the server's exit code, or with the signal number + 128 in case a signal was received). Afterward, if the shell received SIGTERM, it will call the _term
function specified as the SIGTERM trap handler before exiting (in which we do any cleanup and manually propagate the signal to the server process using kill
).
Bash does not forward signals like SIGTERM to processes it is currently waiting on. If you want to end your script by segueing into your server (allowing it to handle signals and anything else, as if you had started the server directly), you should use exec
, which will replace the shell with the process being opened:
#!/bin/bash
echo "Doing some initial work....";
exec /bin/start/main/server --nodaemon
If you need to keep the shell around for some reason (ie. you need to do some cleanup after the server terminates), you should use a combination of trap
, wait
, and kill
. See SensorSmith's answer.
Andreas Veithen points out that if you do not need to return from the call (like in the OP's example) simply calling through the exec
command is sufficient (@Stuart P. Bentley's answer). Otherwise the "traditional" trap 'kill $CHILDPID' TERM
(@cuonglm's answer) is a start, but the wait
call actually returns after the trap handler runs which can still be before the child process actually exits. So an "extra" call to wait
is advisable (@user1463361's answer).
While this is an improvement it still has a race condition which means that the process may never exit (unless the signaler retries sending the TERM signal). The window of vulnerability is between registering the trap handler and recording the child's PID.
The following eliminates that vulnerability (packaged in functions for reuse).
prep_term()
{
unset term_child_pid
unset term_kill_needed
trap 'handle_term' TERM INT
}
handle_term()
{
if [ "${term_child_pid}" ]; then
kill -TERM "${term_child_pid}" 2>/dev/null
else
term_kill_needed="yes"
fi
}
wait_term()
{
term_child_pid=$!
if [ "${term_kill_needed}" ]; then
kill -TERM "${term_child_pid}" 2>/dev/null
fi
wait ${term_child_pid} 2>/dev/null
trap - TERM INT
wait ${term_child_pid} 2>/dev/null
}
# EXAMPLE USAGE
prep_term
/bin/something &
wait_term