bash 陷阱中断命令,但应在循环结束时退出

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/26808727/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-18 11:45:11  来源:igfitidea点击:

bash trap interrupt command but should exit on end of loop

bashshellloops

提问by UsersUser

I′ve asked Bash trap - exit only at the end of loopand the submitted solution works but while pressing CTRL-C the running command in the script (mp3convert with lame) will be interrupt and than the complete for loop will running to the end. Let me show you the simple script:

我问过Bash 陷阱 - 仅在循环结束时退出,提交的解决方案有效,但在按下 CTRL-C 时,脚本中的运行命令(mp3convert with lame)将被中断,然后完整的 for 循环将运行到最后. 让我向您展示一个简单的脚本:

#!/bin/bash
mp3convert () { lame -V0 file.wav file.mp3 }

PreTrap() { QUIT=1 }

CleanUp() {
  if [ ! -z $QUIT ]; then
     rm -f $TMPFILE1
     rm -f $TMPFILE2
     echo "... done!" && exit
  fi }

trap PreTrap SIGINT SIGTERM SIGTSTP
trap CleanUp EXIT

case  in
     write)
           while [ -n "$line" ]
             do
                mp3convert
                [SOMEMOREMAGIC]
                CleanUp
             done
    ;;
QUIT=1

If I press CTRL-C while function mp3convert is running the lame command will be interrupt and then [SOMEMOREMAGIC] will execute before CleanUp is running. I don′t understand why the lame command will be interrupt and how I could avoid them.

如果我在函数 mp3convert 运行时按 CTRL-C,跛脚命令将被中断,然后 [SOMEMOREMAGIC] 将在 CleanUp 运行之前执行。我不明白为什么蹩脚的命令会被中断以及我如何避免它们。

采纳答案by Robin Hsu

Try to simplify the discussion above, I wrap up an easier understandable version of show-case script below. This script also HANDLES the "double control-C problem": (Double control-C problem: If you hit control C twice, or three times, depending on how many wait $PIDyou used, those clean up can not be done properly.)

尽量简化上面的讨论,我在下面总结了一个更容易理解的展示脚本版本。该脚本还处理了“双控制-C问题”:(双控制-C问题:如果您按控制C两次或三下,取决于wait $PID您使用的次数,那些清理工作将无法正确完成。)

#!/bin/bash

mp3convert () {
  echo "mp3convert..."; sleep 5; echo "mp3convert done..."
}

PreTrap() {
  echo "in trap"
  QUIT=1
  echo "exiting trap..."
}

CleanUp() {
  ### Since 'wait $PID' can be interrupted by ^C, we need to protected it
  ### by the 'kill' loop  ==> double/triple control-C problem.
  while kill -0 $PID >& /dev/null; do wait $PID; echo "check again"; done

  ### This won't work (A simple wait $PID is vulnerable to double control C)
  # wait $PID

  if [ ! -z $QUIT ]; then
     echo "clean up..."
     exit
 fi
}

trap PreTrap SIGINT SIGTERM SIGTSTP
#trap CleanUp EXIT

for loop in 1 2 3; do
    (
      echo "loop #$loop"
      mp3convert
      echo magic 1
      echo magic 2
      echo magic 3
    ) &
    PID=$!
    CleanUp
    echo "done loop #$loop"
done

The kill -0trick can be found in a comment of this link

kill -0可以在此链接的评论中找到该技巧

回答by mattias

One way of doing this would be to simply disable the interrupt until your program is done. Some pseudo code follows:

一种方法是简单地禁用中断,直到您的程序完成。部分伪代码如下:

#!/bin/bash

# First, store your stty settings and disable the interrupt
STTY=$(stty -g) 
stty intr undef

#run your program here
runMp3Convert()

#restore stty settings
stty ${STTY}

# eof

Another idea would be to run your bash script in the background (if possible).

另一个想法是在后台运行您的 bash 脚本(如果可能)。

mp3convert.sh &

or even,

甚至,

nohup mp3convert.sh &

回答by PM 2Ring

When you hit Ctrl-C in a terminal, SIGINT gets sent to all processes in the foreground process group of that terminal, as described in this Stack Exchange "Unix & Linux" answer: How Ctrl C works. (The other answers in that thread are well worth reading, too). And that's why your mp3convert function gets interrupted even though you have set a SIGINT trap.

当您在终端中按下 Ctrl-C 时,SIGINT 将被发送到该终端的前台进程组中的所有进程,如 Stack Exchange“Unix & Linux”答案中所述:Ctrl C 的工作原理。(该线程中的其他答案也非常值得一读)。这就是为什么即使您设置了 SIGINT 陷阱,您的 mp3convert 函数也会被中断的原因。

But you can get around that by running the mp3convert function in the background, as mattias mentioned. Here's a variation of your script that demonstrates the technique.

但是你可以通过在后台运行 mp3convert 函数来解决这个问题,正如马蒂亚斯所提到的。这是演示该技术的脚本的变体。

#!/usr/bin/env bash

myfunc() 
{
    echo -n "Starting  :"
    for i in {1..7}
    do
        echo -n " $i"
        sleep 1
    done
    echo ". Finished "
}

PreTrap() { QUIT=1; echo -n " in trap "; }

CleanUp() {
    #Don't start cleanup until current run of myfunc is completed.
    wait $pid
    [[ -n $QUIT ]] &&
    {
        QUIT=''
        echo "Cleaning up"
        sleep 1
        echo "... done!" && exit
    } 
}

trap PreTrap SIGINT SIGTERM SIGTSTP
trap CleanUp EXIT

for i in {a..e}
do
    #Run myfunc in background but wait until it completes.
    myfunc "$i" &
    pid=$!
    wait $pid
    CleanUp
done

QUIT=1

When you hit Ctrl-C while myfuncis in the middle of a run, PreTrapprints its message and sets the QUIT flag, but myfunccontinues running and CleanUpdoesn't commence until the current myfuncrun has finished.

当您在运行过程中按 Ctrl-C 时myfuncPreTrap打印其消息并设置 QUIT 标志,但会myfunc继续运行并且CleanUp直到当前myfunc运行完成后才会开始。

Note that my version of CleanUpresets the QUIT flag. This prevents CleanUpfrom running twice.

请注意,我的版本CleanUp重置了 QUIT 标志。这可以防止CleanUp运行两次。



This version removes the CleanUpcall from the main loop and puts it inside the PreTrapfunction. It uses waitwith no ID argument in PreTrap, which means we don't need to bother saving the PID of each child process. This should be ok since if we're in the trap we dowant to wait for all child processes to complete before proceeding.

此版本CleanUp从主循环中删除调用并将其放入PreTrap函数中。它在 中使用wait没有 ID 参数PreTrap,这意味着我们不需要费心保存每个子进程的 PID。这应该没问题,因为如果我们处于陷阱中,我们确实希望在继续之前等待所有子进程完成。

#!/bin/bash

# Yet another Trap demo...

myfunc() 
{
    echo -n "Starting  :"
    for i in {1..5}
    do
        echo -n " $i"
        sleep 1
    done
    echo ". Finished "
}

PreTrap() { echo -n " in trap "; wait; CleanUp; }

CleanUp() {
    [[ -n $CLEAN ]] && { echo bye; exit; }

    echo "Cleaning up"
    sleep 1
    echo "... done!" 
    CLEAN=1

    exit
}

trap PreTrap SIGINT SIGTERM SIGTSTP
trap "echo exittrap; CleanUp" EXIT

for i in {a..c}
do
    #Run myfunc in background but wait until it completes.
    myfunc "$i" &  wait $!
done

We don't really need to do myfunc "$i" & wait $!in this script, it could be simplified even further to myfunc "$i" & wait. But generally it's better to wait for a specific PID just in case there's some other process running in the background that we don'twant to wait for.

我们真的不需要myfunc "$i" & wait $!在这个脚本中做,它可以进一步简化为myfunc "$i" & wait. 但一般最好是等待一个特定的PID,以防万一有在后台运行,我们一些其他进程希望等待。

Note that pressing Ctrl-C while CleanUpitself is running will interrupt the current foreground process (probably sleepin this demo).

请注意,在CleanUp其自身运行时按 Ctrl-C将中断当前的前台进程(可能sleep在此演示中)。