web-dev-qa-db-ja.com

SIGINTが関数呼び出しと子プロセスを中断しないようにします

次のスクリプトについて考えてみます。

#!/bin/bash

set -o pipefail
set -o history

trapper() {
   func="$1" ; shift
   for sig ; do
      trap "$func $sig" "$sig"
   done
}

err_handler () {
  case $2 in
    INT)
       stop_received=1
    ;;
    TSTP)
    ;;
    ERR)
       if [[ $2 != "INT" ]]; then # for some reason, ERR gets triggered on SIGINT
          code=$?
          if [ $code -ne 0 ]; then
             echo "Failed on line $1"
             echo "$BASH_COMMAND returned $?"
             echo "Content of variables at the time of failure:"
             echo "$(set -o posix; set)"
             exit 1
          fi
       fi
    ;;
  esac
}

main() {
   ping -c 5 www.google.com # this is a test to see if INT interrupts main()
   # do a bunch of stuff, I mean a real bunch of stuff, like check some
   # files, do some processing, connect to a database,
   # do more processing, move some files around, you get the drift
}

exec > >(tee -a my.log)
exec 2>&1

trapper 'err_handler $LINENO' INT TSTP ERR
while main
do
  if [[ "$stop_received" == "1" ]]; then
     break
  fi
  setsid sleep 2 & wait
done
trap ERR

私が達成しようとしているのは、関数main()がゼロ以外の値を返すか、エラーが発生するか、SIGINTを受信するまで、スクリプトを無限ループで実行することです。

ただし、SIGINTがmain()の実行を停止することは望ましくありません。つまり、スクリプトがSIGINTを受信した場合、main()が終了するのを待ってから、正常に終了する必要があります。しかし、CTRL + Cを押すと、pingが中断されていることがわかります。現時点では、これが機能するかどうかを確認するために、main()の下にあるすべてをコメントアウトしました。 pingが中断されるため、main()の下にある他のコマンドも中断されると想定しています。 pingが中断されると、処理は$ stop_received = 1かどうかを確認する行にジャンプし、ループが中断してスクリプトが終了します。ブレークをエコーだけに置き換えると、スクリプトはwhileメインループの次の反復に進むだけです。

SIGINTが現在実行中のコマンドを中断しないようにするにはどうすればよいですか?私のスクリプトはデータベース内のDMLステートメントを含む多くのことを行うので、main()を中断すると多くの悲しみが生じます。

次に、スクリプトはctrl + zもトラップしません。むしろ、スクリプトがctrl + zでスタックし、killpidを終了する必要があります。スリープはスクリプト自体ではなくbashの子であると想定しましたが、ctrl + zを使用するとスクリプトが一時停止し、スリープ状態が不安定な状態になります。したがって、setsidはスリープを待ちますが、それでもハングします。

ありがとう。

2
Jim Walker

の影響を遮断する方法はいくつかあります Ctrl+C

  • 信号が発生しないように端子設定を変更してください。
  • 信号がブロック解除されたときに後で配信できるように、信号をブロックします。
  • 信号を無視するか、ハンドラーを設定します。
  • バックグラウンドプロセスグループでサブプロセスを実行します。

あなたはそれを検出したいので Ctrl+C 信号が出ていることを無視して、押されました。端末の設定を変更することもできますが、その場合はカスタムキー処理コードを作成する必要があります。シェルはシグナルブロッキングへのアクセスを提供しません。

ただし、サブプロセスを別のプロセスグループで実行することにより、サブプロセスをシグナルの受信から自動的に分離することができます。対話型シェルはデフォルトで別のプロセスグループでバックグラウンドコマンドを実行しますが、非対話型シェルは同じプロセスグループでそれらを実行し、フォアグラウンドプロセスグループ内のすべてのプロセスはターミナルイベントから信号を受信します。シェルに別のプロセスグループでバックグラウンドジョブを実行するには、set -m。ランニング setsid ping …は、pingを別のプロセスグループで実行するように強制する別の方法です。

set -m
interrupted=
trap 'echo Interrupted, but ping may still be running' INT
set -m
ping … &
while wait; [ $? -ge 128 ]; do echo "Waiting for background jobs"; done
echo ping has finished

お望みならば Ctrl+Z バックグラウンドプロセスグループを一時停止するには、シェルからのシグナルを伝播する必要があります。

シェルスクリプトでは、信号を細かく制御するのは少し難しいです。ATTksh以外のシェルは、コーナーケースに到達すると少しバグが発生する傾向があるため、Perlなどのより詳細な制御が可能な言語を検討してください。PythonまたはRuby。