web-dev-qa-db-ja.com

KeyboardInterrupt例外を使用したSIGINTのキャプチャは、スクリプトではなくターミナルで機能します

Python 2.7プログラムでSIGINT(またはキーボード割り込み)をキャッチしようとしています。これが私のPythonテストスクリプトtestルックス:

#!/usr/bin/python

import time

try:
    time.sleep(100)
except KeyboardInterrupt:
    pass
except:
    print "error"

次に、シェルスクリプトtest.shがあります:

./test & pid=$!
sleep 1
kill -s 2 $pid

Bash、sh、またはbash test.shを使用してスクリプトを実行すると、Pythonプロセスtestは実行されたままになり、SIGINTで強制終了できません。 。一方、test.shコマンドをコピーして(bash)ターミナルに貼り付けると、Pythonプロセスtestがシャットダウンします。

何が起こっているのかわからないので、理解したい。では、違いはどこにあり、なぜですか?

これは、PythonでSIGINTをキャッチする方法ではありません!docs によると、これが方法です。動作するはずです:

Pythonはデフォルトで少数のシグナルハンドラーをインストールします:SIGPIPE ...そしてSIGINTはKeyboardInterrupt例外に変換されます

プログラムがシェルから直接開始された場合、KeyboardInterruptSIGINTによって送信されたときに、実際にkillをキャッチしていますが、プログラムがバックグラウンドで実行されたbashスクリプトから開始された場合、そのKeyboardInterruptは決して発生しません。

14
Velda

デフォルトのsigintハンドラーが起動時にnotインストールされていない場合があります。それは、シグナルマスクにSIGINTの_SIG_IGN_が含まれている場合です。プログラムの起動。これを担当するコードは ここ にあります。

無視されたシグナルのシグナルマスクは親プロセスから継承され、処理されたシグナルは_SIG_DFL_にリセットされます。したがって、SIGINTが無視された場合、ソースの条件if (Handlers[SIGINT].func == DefaultHandler)はトリガーされず、デフォルトハンドラーがインストールされません。pythonは、親によって行われた設定をオーバーライドしません。この場合のプロセス。

それでは、さまざまな状況で使用されるシグナルハンドラーを表示してみましょう。

_# invocation from interactive Shell
$ python -c "import signal; print(signal.getsignal(signal.SIGINT))"
<built-in function default_int_handler>

# background job in interactive Shell
$ python -c "import signal; print(signal.getsignal(signal.SIGINT))" &
<built-in function default_int_handler>

# invocation in non interactive Shell
$ sh -c 'python -c "import signal; print(signal.getsignal(signal.SIGINT))"'
<built-in function default_int_handler>

# background job in non-interactive Shell
$ sh -c 'python -c "import signal; print(signal.getsignal(signal.SIGINT))" &'
1
_

したがって、最後の例では、SIGINTは1(_SIG_IGN_)に設定されています。これは、シェルスクリプトでバックグラウンドジョブを開始する場合と同じです。これらはデフォルトで非対話型であるためです(Shebangで_-i_オプションを使用する場合を除く)。

したがって、これは、非対話型シェルセッションでバックグラウンドジョブを起動するときにシェルがシグナルを無視するためであり、python直接ではありません。少なくともbashdashはこのように動作しますが、試していません。他のシェル。

この状況に対処するには、次の2つのオプションがあります。

  • デフォルトのシグナルハンドラを手動でインストールします。

    _import signal
    signal.signal(signal.SIGINT, signal.default_int_handler)
    _
  • _-i_オプションをシェルスクリプトのシバンに追加します。例:

    _#!/bin/sh -i
    _

編集:この動作はbashマニュアルに記載されています:

信号
.。
ジョブ制御が有効になっていない場合、非同期コマンドは、これらの継承されたハンドラーに加えて、SIGINTとSIGQUITを無視します。

これは、デフォルトでジョブ制御が無効になっている非対話型シェルに適用され、実際にはPOSIXで指定されています。 シェルコマンド言語

23
mata