私はscript
でラップし、Pythonスクリプトを使用してsubprocess.Popen
。ユーザーがSIGINT
を発行した場合に確実に終了するようにしています。
プロセスが少なくとも2つの方法で中断されたかどうかを確認できました。
A.ラップされたコマンドの終了ステータスがゼロ以外の場合は死にます(script
は常に0を返すため、機能しません)。
B.サブプロセスを単に中断するのではなく、親のPythonスクリプトでSIGINT
を使用して特別なことを行います。私は以下を試しました:
import sys
import signal
import subprocess
def interrupt_handler(signum, frame):
print "While there is a 'script' subprocess alive, this handler won't executes"
sys.exit(1)
signal.signal(signal.SIGINT, interrupt_handler)
for n in range( 10 ):
print "Going to sleep for 2 second...Ctrl-C to exit the sleep cycles"
# exit 1 if we make it to the end of our sleep
cmd = [ 'script', '-q', '-c', "sleep 2 && (exit 1)", '/dev/null']
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
while True:
if p.poll() != None :
break
else :
pass
# Exiting on non-zero exit status would suffice
print "Exit status (script always exits zero, despite what happened to the wrapped command):", p.returncode
Ctrl-Cを押してpythonスクリプトを終了します。代わりにサブプロセスが停止し、スクリプトが続行します。
このハックは機能しますが、醜いです...
コマンドを次のように変更します。
success_flag = '/tmp/success.flag'
cmd = [ 'script', '-q', '-c', "sleep 2 && touch " + success_flag, '/dev/null']
そして、置きます
if os.path.isfile( success_flag ) :
os.remove( success_flag )
else :
return
forループの終わり
サブプロセスは、デフォルトでは同じプロセスグループの一部であり、ターミナルからの信号を制御および受信できるのは1つだけなので、いくつかの異なるソリューションがあります。
stdinをPIPEとして設定(親プロセスからの継承とは対照的に)、これにより、子プロセスがそれに関連付けられたシグナルを受信できなくなります。
subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)
親プロセスグループから切り離す、子はシグナルを受信しなくなります
def preexec_function():
os.setpgrp()
subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, preexec_fn=preexec_function)
子プロセスのシグナルを明示的に無視
def preexec_function():
signal.signal(signal.SIGINT, signal.SIG_IGN)
subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, preexec_fn=preexec_function)
ただし、これは子プロセスによって上書きされる可能性があります。
こぶし物; Popen
オブジェクトにはsend_signal()
メソッドがあります。起動したシグナルにシグナルを送信する場合は、このメソッドを使用してシグナルを送信します。
第二のもの;サブプロセスとの通信をセットアップする方法にさらに問題があり、サブプロセスと通信しないサブプロセスにその出力を_subprocess.PIPE
_に送信して、パイプから読み取らないように安全に指示することはできません。 UNIXパイプはバッファリングされ(通常4Kバッファ?)、サブプロセスがバッファを満たし、パイプの反対側のプロセスがバッファリングされたデータを読み取らない場合、サブプロセスは保留されます(ロックアップ、オブザーバーの観点から)。 )パイプへの次の書き込み時。したがって、_subprocess.PIPE
_を使用するときの通常のパターンは、Popen
オブジェクトでcommunicate()
を呼び出すことです。
サブプロセスからデータを戻す場合は、_subprocess.PIPE
_を使用する必要はありません。クールなトリックは、_tempfile.TemporaryFile
_クラスを使用して名前のない一時ファイルを作成することです(実際にファイルを開き、ファイルシステムからiノードをすぐに削除するため、ファイルにアクセスできますが、他の誰もファイルを開くことはできません) 。次のようなことができます。
_with tempfile.TemporaryFile() as iofile:
p = Popen(cmd, stdout=iofile, stderr=iofile)
while True:
if p.poll() is not None:
break
else:
time.sleep(0.1) # without some sleep, this polling is VERY busy...
_
次に、パイプを使用する代わりに、サブプロセスが終了したことがわかっているときに、一時ファイルの内容を読み取ることができます(実行する前に、ファイルの先頭を探します)。パイプバッファリングの問題は、サブプロセスの出力がファイルに送られる場合(一時的かどうかにかかわらず)問題にはなりません。
これはあなたが望むことをするコードサンプルのリフです。シグナルハンドラーは、親プロセス(この例では、SIGINTおよびSIGTERM)によってトラップされているシグナルを現在のすべてのサブプロセス(このプログラムには1つだけ存在する必要があります)に繰り返し、次のシャットダウンを示すモジュールレベルのフラグを設定します機会。私は_subprocess.PIPE
_ I/Oを使用しているので、Popen
オブジェクトでcommunicate()
を呼び出します。
_#!/usr/bin/env python
from subprocess import Popen, PIPE
import signal
import sys
current_subprocs = set()
shutdown = False
def handle_signal(signum, frame):
# send signal recieved to subprocesses
global shutdown
shutdown = True
for proc in current_subprocs:
if proc.poll() is None:
proc.send_signal(signum)
signal.signal(signal.SIGINT, handle_signal)
signal.signal(signal.SIGTERM, handle_signal)
for _ in range(10):
if shutdown:
break
cmd = ["sleep", "2"]
p = Popen(cmd, stdout=PIPE, stderr=PIPE)
current_subprocs.add(p)
out, err = p.communicate()
current_subprocs.remove(p)
print "subproc returncode", p.returncode
_
そしてそれを呼び出す(3番目の2秒間隔で_Ctrl-C
_を使用):
_% python /tmp/proctest.py
subproc returncode 0
subproc returncode 0
^Csubproc returncode -2
_
(例のように)プロセスが生成された後に行うpython処理がない場合は、サブプロセスモジュールの代わりにos.execvpを使用するのが最も簡単な方法です。サブプロセスはpythonプロセスを完全に置き換え、SIGINTを直接キャッチするプロセスになります。
スクリプトのマニュアルページに-eスイッチが見つかりました。
-e Return the exit code of the child process. Uses the same format
as bash termination on signal termination exit code is 128+n.
128 + nが何であるかはよくわかりませんが、ctrl-cに対して130を返すようです。だからあなたのcmdを次のように変更する
cmd = [ 'script', '-e', '-q', '-c', "sleep 2 && (exit 1)", '/dev/null']
そして置く
if p.returncode == 130:
break
forループの最後で、あなたが望むことをしているようです。