bash trap interrupt command but should exit on end

2019-07-22 03:06发布

问题:

I´ve asked Bash trap - exit only at the end of loop and 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:

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

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.

回答1:

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 $PID you used, those clean up can not be done properly.)

#!/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 -0 trick can be found in a comment of this link



回答2:

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).

mp3convert.sh &

or even,

nohup mp3convert.sh &


回答3:

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.

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.

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

When you hit Ctrl-C while myfunc is in the middle of a run, PreTrap prints its message and sets the QUIT flag, but myfunc continues running and CleanUp doesn't commence until the current myfunc run has finished.

Note that my version of CleanUp resets the QUIT flag. This prevents CleanUp from running twice.


This version removes the CleanUp call from the main loop and puts it inside the PreTrap function. It uses wait with 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 do want to wait for all child processes to complete before proceeding.

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

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't want to wait for.

Note that pressing Ctrl-C while CleanUp itself is running will interrupt the current foreground process (probably sleep in this demo).



标签: bash shell loops