41

scriptを使用してPythonスクリプトからラップして生成するコマンドがありsubprocess.Popenます。ユーザーがを発行した場合に確実に停止するようにしていますSIGINT

プロセスが少なくとも2つの方法で中断されたかどうかを把握できました。

A.ラップされたコマンドの終了ステータスがゼロ以外の場合は終了します(script常に0を返すように見えるため、機能しません)

SIGINTB.サブプロセスを単に中断するのではなく、親Pythonスクリプトで何か特別なことをします。私は次のことを試しました:

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スクリプトを終了したいのですが。代わりに起こっているのは、サブプロセスが停止し、スクリプトが続行することです。

4

5 に答える 5

32

サブプロセスはデフォルトで同じプロセスグループの一部であり、ターミナルからの信号を制御および受信できるのは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)

ただし、これは子プロセスによって上書きされる可能性があります。

于 2012-12-06T05:23:30.243 に答える
12

最初のもの; オブジェクトにsend_signal()メソッドがありPopenます。起動したものにシグナルを送信する場合は、このメソッドを使用して送信します。

第二に; サブプロセスとの通信を設定する方法と、それと通信しない方法に関するより深い問題。subprocess.PIPEサブプロセスに、出力を送信してからパイプから読み取らないように安全に指示することはできません。UNIXパイプはバッファリングされ(通常は4Kバッファ?)、サブプロセスがバッファをいっぱいにし、パイプの反対側のプロセスがバッファリングされたデータを読み取らない場合、サブプロセスは保留されます(オブザーバーの観点からロックアップします) ) パイプへの次の書き込み時に。したがって、使用時の通常のパターンは、オブジェクトsubprocess.PIPEを呼び出すことです。communicate()Popen

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.PIPEI/Oを使用しているのでcommunicate()Popenオブジェクトを呼び出します。

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

そしてそれを呼び出します(Ctrl-C3番目の2秒間隔で a を使用):

% python /tmp/proctest.py 
subproc returncode 0
subproc returncode 0
^Csubproc returncode -2
于 2012-12-09T16:26:52.277 に答える
1

プロセスが生成された後に実行するPython処理がない場合(例のように)、最も簡単な方法は、サブプロセスモジュールの代わりにos.execvpを使用することです。サブプロセスはPythonプロセスを完全に置き換え、SIGINTを直接キャッチするサブプロセスになります。

于 2012-12-08T15:29:05.303 に答える
1

このハックは機能しますが、醜いです...

コマンドを次のように変更します。

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ループの最後に

于 2012-12-05T21:38:55.207 に答える
0

スクリプトのマニュアル ページで -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ループの最後で、あなたが望むことをしているようです。

于 2012-12-04T20:36:46.563 に答える