Bash 在 rsync/subshell exec 语句期间不捕获中断
题
语境:
我有一个 bash 脚本,其中包含一个子 shell 和一个用于 EXIT 伪信号的陷阱,并且它在执行期间没有正确捕获中断 rsync
. 。这是一个例子:
#!/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
该脚本的想法是这样的:大多数重要的逻辑都在通过管道传输的子 shell 中运行 tee
并记录到日志文件中,所以我不必 tee
主逻辑的每一行都将其全部记录下来。每当子 shell 结束或脚本因任何原因停止时(EXIT 伪信号应该捕获所有这些情况),陷阱将拦截它并运行 cleanup()
功能,然后移除陷阱。这 rsync
和 sleep
命令(睡眠只是一个例子)运行 exec
如果我在运行时杀死父脚本,则可以防止创建僵尸进程,并且每个可能长时间运行的命令都包含在其自己的子 shell 中,以便当 exec
完成后,它不会终止整个脚本。
问题:
如果我中断脚本(通过 kill
或 CTRL+C) 在 exec/subshell 包装期间 sleep
命令,陷阱正常工作,我看到“清理!”回声和记录。如果我在执行过程中中断脚本 rsync
命令,我明白了 rsync
结束,并写下 rsync error: received SIGINT, SIGTERM, or SIGHUP (code 20) at rsync.c(544) [sender=3.0.6]
到屏幕上,然后脚本就消失了;无需清理,无需捕获。为什么不打断/杀死 rsync
触发陷阱?
我尝试过使用 --no-detach
用rsync切换,但没有改变任何东西。我有 bash 4.1.2、rsync 3.0.6、centOS 6.2。
解决方案
如何将 X 点的所有输出重定向到 tee,而不必在各处重复它并扰乱所有子 shell 和执行程序......(希望我没有错过什么)
#!/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
其他提示
此外 set -e
, ,我想你想要 set -E
:
如果设置,则 ERR 上的任何陷阱都会由 shell 函数、命令替换和在子 shell 环境中执行的命令继承。在这种情况下,通常不会继承 ERR 陷阱。
或者,不要使用大括号将命令包装在子 shell 中,这仍然使您能够重定向命令输出,但会在当前 shell 中执行它们。
如果将 INT 添加到陷阱中,中断将被正确捕获
trap '{
(cleanup;) | 2>&1 tee -a $logfile
}' EXIT INT
Bash 正确捕获中断。然而,这并没有回答这个问题,为什么脚本在退出时会陷入困境: sleep
被中断,也没有为什么它不触发 rsync
, ,但使脚本按预期工作。希望这可以帮助。
您的 shell 可能配置为出现错误时退出:
bash # enter subshell
set -e
trap "echo woah" EXIT
sleep 4
如果你打断 sleep
(^C) 那么子 shell 将退出 set -e
并打印 woah
正在进行中。
另外,稍微无关:你的 trap - EXIT
位于子 shell 中(显式),因此在 cleanup 函数返回后不会产生任何效果
从实验中可以很清楚地看出 rsync
其行为类似于其他工具,例如 ping
并且不继承来自调用 Bash 父级的信号。
因此,您必须对此发挥一点创意,并执行以下操作:
$ cat rsync.bash
#!/bin/sh
set -m
trap '' SIGINT SIGTERM EXIT
rsync -avz LargeTestFile.500M root@host.mydom.com:/tmp/. &
wait
echo FIN
现在当我运行它时:
$ ./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
我们可以看到文件已完全传输:
$ ll -h | grep Large
-rw-------. 1 501 games 500M Jul 9 21:44 LargeTestFile.500M
怎么运行的
这里的技巧是我们通过以下方式告诉 Bash set -m
禁用其中任何后台作业的作业控制。然后我们将背景设置为 rsync
然后运行 wait
命令将等待最后运行的命令, rsync
, ,直到完成。
然后我们用以下方法保护整个脚本 trap '' SIGINT SIGTERM EXIT
.