Linux Bash在rsync/subshell exec语句期间不捕获中断

Linux Bash在rsync/subshell exec语句期间不捕获中断,linux,bash,shell,rsync,bash-trap,Linux,Bash,Shell,Rsync,Bash Trap,上下文: 我有一个bash脚本,它包含一个子shell和一个用于退出伪信号的陷阱,并且它在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;

上下文:

我有一个bash脚本,它包含一个子shell和一个用于退出伪信号的陷阱,并且它在
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中,该子shell通过
tee
管道传输到一个日志文件,因此我不必
tee
主逻辑的每一行都将其记录下来。每当子shell结束,或脚本因任何原因停止时(退出伪信号应捕获所有这些情况),陷阱将拦截它并运行
cleanup()
函数,然后移除陷阱。
rsync
sleep
命令(sleep只是一个示例)通过
exec
运行,以防止在父脚本运行时杀死它们时创建僵尸进程,并且每个可能长时间运行的命令都包装在自己的子shell中,这样当
exec
完成时,它不会终止整个脚本

问题:

如果我在exec/subshell wrapped
sleep
命令期间中断脚本(通过
kill
或CTRL+C),陷阱工作正常,我会看到“Cleaning up!”回声并记录。如果在执行
rsync
命令期间中断脚本,我会看到
rsync
结束,并将
rsync错误:在rsync.c(544)[sender=3.0.6]
处接收到的SIGINT、SIGTERM或SIGHUP(代码20)写入屏幕,然后脚本就消失了;没有清理,没有陷阱。为什么中断/终止
rsync
不会触发陷阱

我曾尝试将
--no detach
开关与rsync一起使用,但它没有改变任何东西。
我有bash4.1.2、rsync3.0.6和centos6.2。如果您将INT添加到陷阱中,中断将被正确捕获

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

Bash正在正确捕获中断。但是,这并不能回答这样一个问题:如果
睡眠
被中断,为什么脚本会在退出时陷入陷阱,或者为什么它不会在
rsync
上触发,而是使脚本按预期工作。希望这有帮助。

您的shell可能被配置为在出现错误时退出:

bash # enter subshell
set -e
trap "echo woah" EXIT
sleep 4
如果中断
sleep
(^C),则子shell将由于
set-e
而退出,并在此过程中打印
woah


另外,有点不相关:您的
陷阱-退出
位于子shell(显式)中,因此在清理函数返回后它不会产生效果

除了
set-e
,我想您需要
set-e

如果设置了ERR,则shell函数、命令替换和在子shell环境中执行的命令将继承ERR上的任何陷阱。在这种情况下,错误陷阱通常不会被继承


或者,不要将命令包装在子shell中,而是使用大括号,这样仍然可以重定向命令输出,但可以在当前shell中执行它们。

将点X的所有输出重定向到tee,而不必到处重复,并将所有子shell和执行都弄乱。。。(希望我没有错过什么)


从实验中可以非常清楚地看到,
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
工作原理 这里的诀窍是,我们通过
set-m
告诉Bash对其中的任何后台作业禁用作业控制。然后我们将
rsync
后台接地,然后运行
wait
命令,该命令将等待最后一个运行命令
rsync
,直到它完成

然后,我们使用
trap''SIGINT SIGTERM EXIT
保护整个脚本

工具书类

这不是问题的原因,但您的日志记录不可靠,因为您同时使用两个不同的程序写入同一个文件。您的
陷阱-退出
位于子shell中(显式),因此,在清理函数returns之后,它不会产生任何效果。在子shell中运行exec与正常运行命令是一样的——您不需要额外的标点符号。这看起来不错,但我实现了它,它产生了与预期相反的效果。如果我将INT添加到陷阱调用中,
sleep
语句不再触发陷阱,
rsync
也不会触发陷阱。当我删除INT(不更改任何其他内容)
sleep
再次触发陷阱。删除上一个“以防万一”陷阱,脚本也可以很好地处理退出陷阱。然而,陷阱中调用清理的INT应该是处理中断的正确方法。与此无关,但正如sehe所写,子shell中的陷阱没有任何作用。这实际上是我的第一个解决方案,但我们的一些环境无法进行重定向,因为有一个奇怪的Bash版本或其他什么……所以我求助于“将整个东西包装在一个块中,并将其全部指向tee”黑客。好的观点。@ZacB你能更具体地描述一下“怪异版本的bash”吗?在某些情况下(当作为
/bin/sh
运行时),它运行n Posix兼容模式。。。如果是这种情况,则在执行之前添加
set+o posix
,这就是@nhed的情况;通过切换到
/bin/bash
解决了此问题。
$ ll -h | grep Large
-rw-------. 1  501 games 500M Jul  9 21:44 LargeTestFile.500M