【问题标题】:Bash not trapping interrupts during rsync/subshell exec statementsBash 在 rsync/subshel​​l exec 语句期间没有捕获中断
【发布时间】:2012-03-26 08:47:18
【问题描述】:

上下文:

我有一个 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中运行,该子shell通过tee和一个日志文件传输,所以我不必tee主逻辑的每一行来全部记录下来。每当 subshel​​l 结束,或脚本因任何原因停止(EXIT 伪信号应捕获所有这些情况)时,陷阱将拦截它并运行cleanup() 函数,然后删除陷阱。 rsyncsleep 命令(sleep 只是一个示例)通过 exec 运行,以防止创建僵尸进程,如果我在父脚本运行时杀死它们,并且每个可能长时间运行的命令被包裹在自己的子外壳中,因此当exec 完成时,它不会终止整个脚本。

问题:

如果我在 exec/subshel​​l 包装的sleep 命令期间中断脚本(通过kill 或 CTRL+C),陷阱工作正常,我看到“正在清理!”回显并记录。如果我在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。

【问题讨论】:

  • 这不是您的问题的原因,但您的日志记录不可靠,因为您同时使用两个不同的程序写入同一个文件。
  • 你的trap - EXIT在一个子shell中(明确),所以清理函数返回后它不会有效果
  • 在子 shell 中运行 exec 与正常运行命令相同 - 您不需要额外的标点符号。

标签: linux bash shell rsync bash-trap


【解决方案1】:

从实验中可以清楚地看出,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 保护整个脚本。

参考文献

【讨论】:

    【解决方案2】:

    如何将点 X 的所有输出重定向到 tee 而不必到处重复并弄乱所有子 shell 和 exec ......(希望我没有错过任何东西)

    #!/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
    

    【讨论】:

    • 这实际上是我的第一个解决方案,但是由于 Bash 的怪异版本或其他原因,我们的一些环境无法进行重定向......所以我求助于“将整个东西包装在一个块并将其全部指向 tee" hack。不过好点。
    • @ZacB 您能否更具体地了解“怪异版本的 bash”?在某些情况下(当以/bin/sh 运行时)它会运行 n Posix 兼容模式...如果是这种情况,请在 exec 之前添加 set +o posix
    • 情况就是这样@nhed;已通过切换到/bin/bash 解决。
    【解决方案3】:

    除了set -e,我想你还想要set -E

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

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

    【讨论】:

      【解决方案4】:

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

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

      如果您中断sleep (^C),则子shell 将因set -e 而退出,并在此过程中打印woah

      另外,有点不相关:您的 trap - EXIT 位于子外壳中(显式),因此在清理函数返回后它不会产生影响

      【讨论】:

        【解决方案5】:

        如果将 INT 添加到陷阱中,将正确捕获中断

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

        Bash 正确地捕获中断。但是,这并没有回答问题,为什么如果sleep 被中断,脚本会在退出时陷入陷阱,为什么它不会在rsync 上触发,而是使脚本按预期工作。希望这会有所帮助。

        【讨论】:

        • 这看起来是对的,但我实现了它,但它与预期的效果相反。如果我将 INT 添加到陷阱调用中,sleep 语句将不再触发陷阱,rsync 仍然不会。当我删除 INT(没有改变)sleep 再次触发陷阱。
        • 删除最后一个“以防万一”陷阱,脚本应该也可以与 EXIT 陷阱一起正常工作。但是,调用清理的陷阱中的 INT 应该是处理中断的正确方法。不相关,但就像 sehe 写的那样,子外壳中的陷阱什么都不做。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-01-01
        • 2011-06-05
        • 1970-01-01
        • 1970-01-01
        • 2021-12-11
        相关资源
        最近更新 更多