bash trap interrupt命令但应该在循环结束时退出

时间:2014-11-07 19:34:40

标签: bash shell loops

我问过Bash trap - exit only at the end of loop并且提交的解决方案有效,但在按下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 $1 in
     write)
           while [ -n "$line" ]
             do
                mp3convert
                [SOMEMOREMAGIC]
                CleanUp
             done
    ;;
QUIT=1

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

3 个答案:

答案 0 :(得分:2)

尝试简化上面的讨论,我在下面结束了一个更容易理解的show-case脚本版本。这个脚本还处理“双控制-C问题”: (Double control-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

可以在this link

的评论中找到kill -0技巧

答案 1 :(得分:1)

这样做的一种方法是在程序完成之前简单地禁用中断。 一些伪代码如下:

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

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

mp3convert.sh &

甚至,

nohup mp3convert.sh &

答案 2 :(得分:1)

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

但是你可以通过在后台运行mp3convert函数来解决这个问题,就像mattias提到的那样。以下是演示该技术的脚本的变体。

#!/usr/bin/env bash

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

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

myfunc处于运行过程中时按Ctrl-C,PreTrap将打印其消息并设置QUIT标志,但myfunc继续运行CleanUp在当前的myfunc运行完成之前不会开始。

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


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

#!/bin/bash

# Yet another Trap demo...

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

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

我们不需要在此脚本中执行myfunc "$i" & wait $!,它可以进一步简化为myfunc "$i" & wait。但通常最好等待一个特定的PID,以防万一其他进程在后台运行,我们想要等待。

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