Context:
I have a bash script that contains a subshell and a trap for the EXIT pseudosignal, and it's not properly trapping interrupts during an rsync
. Here's an example:
#!/bin/bash
logfile=/path/to/file;
directory1=/path/to/dir
directory2=/path/to/dir
cleanup () {
echo "Cleaning up!"
#do stuff
trap - EXIT
}
trap '{
(cleanup;) | 2>&1 tee -a $logfile
}' EXIT
(
#main script logic, including the following lines:
(exec sleep 10;);
(exec rsync --progress -av --delete $directory1 /var/tmp/$directory2;);
) | 2>&1 tee -a $logfile
trap - EXIT #just in case cleanup isn't called for some reason
The idea of the script is this: most of the important logic runs in a subshell which is piped through tee
and to a logfile, so I don't have to tee
every single line of the main logic to get it all logged. Whenever the subshell ends, or the script is stopped for any reason (the EXIT pseudosignal should capture all of these cases), the trap will intercept it and run the cleanup()
function, and then remove the trap. The rsync
and sleep
commands (the sleep is just an example) are run through exec
to prevent the creation of zombie processes if I kill the parent script while they're running, and each potentially-long-running command is wrapped in its own subshell so that when exec
finishes, it won't terminate the whole script.
The problem:
If I interrupt the script (via kill
or CTRL+C) during the exec/subshell wrapped sleep
command, the trap works properly, and I see "Cleaning up!" echoed and logged. If I interrupt the script during the rsync
command, I see rsync
end, and write rsync error: received SIGINT, SIGTERM, or SIGHUP (code 20) at rsync.c(544) [sender=3.0.6]
to the screen, and then the script just dies; no cleanup, no trapping. Why doesn't an interrupting/killing of rsync
trigger the trap?
I've tried using the --no-detach
switch with rsync, but it didn't change anything.
I have bash 4.1.2, rsync 3.0.6, centOS 6.2.
How about just having all the output from point X be redirected to tee without having to repeat it everywhere and mess with all the sub-shells and execs ... (hope I didn't miss something)
In addition to
set -e
, I think you wantset -E
:Alternatively, instead of wrapping your commands in subshells use curly braces which will still give you the ability to redirect command outputs but will execute them in the current shell.
The interupt will be properly caught if you add INT to the trap
Bash is trapping interrupts correctly. However, this does not anwer the question, why the script traps on exit if
sleep
is interupted, nor why it does not trigger onrsync
, but makes the script work as it is supposed to. Hope this helps.Your shell might be configured to exit on error:
If you interrupt
sleep
(^C) then the subshell will exit due toset -e
and printwoah
in the process.Also, slightly unrelated: your
trap - EXIT
is in a subshell (explicitly), so it won't have an effect after the cleanup function returnsIt's pretty clear from experimentation that
rsync
behaves like other tools such asping
and do not inherit signals from the calling Bash parent.So you have to get a little creative with this and do something like the following:
Now when I run it:
And we can see the file did fully transfer:
How it works
The trick here is we're telling Bash via
set -m
to disable job controls on any background jobs within it. We're then backgrounding thersync
and then running await
command which will wait on the last run command,rsync
, until it's complete.We then guard the entire script with the
trap '' SIGINT SIGTERM EXIT
.References