Bash not trapping interrupts during rsync/subshell exec statements

匆匆过客 提交于 2019-12-18 20:53:17

问题


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.


回答1:


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)

#!/bin/bash
logfile=/path/to/file;
directory1=/path/to/dir
directory2=/path/to/dir

exec > >(exec tee -a $logfile) 2>&1

cleanup () {
     echo "Cleaning up!"
     #do stuff
     trap - EXIT 
}
trap cleanup EXIT

sleep 10
rsync --progress -av --delete $directory1 /var/tmp/$directory2



回答2:


In addition to set -e, I think you want set -E:

If set, any trap on ERR is inherited by shell functions, command substitutions, and commands executed in a sub‐shell environment. The ERR trap is normally not inherited in such cases.

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.




回答3:


The interupt will be properly caught if you add INT to the trap

trap '{
    (cleanup;) | 2>&1 tee -a $logfile
}' EXIT INT

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 on rsync, but makes the script work as it is supposed to. Hope this helps.




回答4:


Your shell might be configured to exit on error:

bash # enter subshell
set -e
trap "echo woah" EXIT
sleep 4

If you interrupt sleep (^C) then the subshell will exit due to set -e and print woah 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 returns




回答5:


It's pretty clear from experimentation that rsync behaves like other tools such as ping 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:

$ cat rsync.bash
#!/bin/sh

 set -m
 trap '' SIGINT SIGTERM EXIT
 rsync -avz LargeTestFile.500M root@host.mydom.com:/tmp/. &
 wait

 echo FIN

Now when I run it:

$ ./rsync.bash
X11 forwarding request failed
building file list ... done
LargeTestFile.500M
^C^C^C^C^C^C^C^C^C^C
sent 509984 bytes  received 42 bytes  92732.00 bytes/sec
total size is 524288000  speedup is 1027.96
FIN

And we can see the file did fully transfer:

$ ll -h | grep Large
-rw-------. 1  501 games 500M Jul  9 21:44 LargeTestFile.500M

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 the rsync and then running a wait 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

  • https://access.redhat.com/solutions/360713
  • https://access.redhat.com/solutions/1539283


来源:https://stackoverflow.com/questions/9624947/bash-not-trapping-interrupts-during-rsync-subshell-exec-statements

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!