これはstdout
データを返す任意のコマンドを実行する、またはゼロ以外の終了コードで例外を発生させるPythonコードです。
proc = subprocess.Popen(
cmd,
stderr=subprocess.STDOUT, # Merge stdout and stderr
stdout=subprocess.PIPE,
Shell=True)
communicate
は、プロセスが終了するのを待つために使用されます。
stdoutdata, stderrdata = proc.communicate()
subprocess
モジュールはタイムアウト(X秒以上実行しているプロセスを強制終了する機能)をサポートしていません。したがって、communicate
は実行に時間がかかることがあります。
WindowsおよびLinux上で実行することを意図したPythonプログラムでタイムアウトを実装する最も簡単な方法は何ですか?
Python 3.3以降の場合:
from subprocess import STDOUT, check_output
output = check_output(cmd, stderr=STDOUT, timeout=seconds)
output
は、コマンドのマージされた標準出力、標準エラーデータを含むバイト文字列です。
このコードは、proc.communicate()
メソッドとは異なり、質問のテキストで指定されているようにゼロ以外の終了ステータスでCalledProcessError
を発生させます。
Shell=True
は不要に使われることが多いので削除しました。 cmd
が本当に必要としている場合はいつでも追加することができます。 Shell=True
を追加した場合、つまり子プロセスが独自の子孫を生成した場合check_output()
は、タイムアウトが示すよりはるかに遅く戻る可能性があります。 サブプロセスタイムアウト失敗 を参照してください。
タイムアウト機能は、Python 2.xの3.2+サブプロセスモジュールの subprocess32
バックポートから利用できます。
低レベルの詳細についてはあまり知りません。しかし、Python 2.6では、APIはスレッドを待機してプロセスを終了する機能を提供しているので、別のスレッドでプロセスを実行することについてはどうでしょうか。
import subprocess, threading
class Command(object):
def __init__(self, cmd):
self.cmd = cmd
self.process = None
def run(self, timeout):
def target():
print 'Thread started'
self.process = subprocess.Popen(self.cmd, Shell=True)
self.process.communicate()
print 'Thread finished'
thread = threading.Thread(target=target)
thread.start()
thread.join(timeout)
if thread.is_alive():
print 'Terminating process'
self.process.terminate()
thread.join()
print self.process.returncode
command = Command("echo 'Process started'; sleep 2; echo 'Process finished'")
command.run(timeout=3)
command.run(timeout=1)
私のマシンでこのスニペットの出力は次のとおりです。
Thread started
Process started
Process finished
Thread finished
0
Thread started
Process started
Terminating process
Thread finished
-15
ここで、最初の実行でプロセスが正常に終了した(戻りコード0)一方、2番目のプロセスでプロセスが終了した(戻りコード-15)ことがわかります。
私は窓でテストしていません。しかし、exampleコマンドを更新すること以外は、thread.joinやprocess.terminateがサポートされていないと言っていることがドキュメントにないので、うまくいくはずだと思います。
jcolladoの答えは threading.Timer クラスを使って簡単にすることができます。
import shlex
from subprocess import Popen, PIPE
from threading import Timer
def run(cmd, timeout_sec):
proc = Popen(shlex.split(cmd), stdout=PIPE, stderr=PIPE)
timer = Timer(timeout_sec, proc.kill)
try:
timer.start()
stdout, stderr = proc.communicate()
finally:
timer.cancel()
# Examples: both take 1 second
run("sleep 1", 5) # process ends normally at 1 second
run("sleep 5", 1) # timeout happens at 1 second
Unixを使っているなら
import signal
...
class Alarm(Exception):
pass
def alarm_handler(signum, frame):
raise Alarm
signal.signal(signal.SIGALRM, alarm_handler)
signal.alarm(5*60) # 5 minutes
try:
stdoutdata, stderrdata = proc.communicate()
signal.alarm(0) # reset the alarm
except Alarm:
print "Oops, taking too long!"
# whatever else
これがAlex Martelliの適切なプロセス強制終了モジュールとしての解決策です。 proc.communicate()を使用しないため、他の方法は機能しません。そのため、大量の出力を生成するプロセスがある場合、そのプロセスは出力バッファをいっぱいにして、そこから何かを読み取るまでブロックします。
from os import kill
from signal import alarm, signal, SIGALRM, SIGKILL
from subprocess import PIPE, Popen
def run(args, cwd = None, Shell = False, kill_tree = True, timeout = -1, env = None):
'''
Run a command with a timeout after which it will be forcibly
killed.
'''
class Alarm(Exception):
pass
def alarm_handler(signum, frame):
raise Alarm
p = Popen(args, Shell = Shell, cwd = cwd, stdout = PIPE, stderr = PIPE, env = env)
if timeout != -1:
signal(SIGALRM, alarm_handler)
alarm(timeout)
try:
stdout, stderr = p.communicate()
if timeout != -1:
alarm(0)
except Alarm:
pids = [p.pid]
if kill_tree:
pids.extend(get_process_children(p.pid))
for pid in pids:
# process might have died before getting to this line
# so wrap to avoid OSError: no such process
try:
kill(pid, SIGKILL)
except OSError:
pass
return -9, '', ''
return p.returncode, stdout, stderr
def get_process_children(pid):
p = Popen('ps --no-headers -o pid --ppid %d' % pid, Shell = True,
stdout = PIPE, stderr = PIPE)
stdout, stderr = p.communicate()
return [int(p) for p in stdout.split()]
if __== '__main__':
print run('find /', Shell = True, timeout = 3)
print run('find', Shell = True)
修正しましたsussudio答え。 (returncode
name__、stdout
name__、stderr
name__、timeout
name__) - stdout
name__およびstderr
name__は、utf-8ストリングにデコードされます。
def kill_proc(proc, timeout):
timeout["value"] = True
proc.kill()
def run(cmd, timeout_sec):
proc = subprocess.Popen(shlex.split(cmd), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
timeout = {"value": False}
timer = Timer(timeout_sec, kill_proc, [proc, timeout])
timer.start()
stdout, stderr = proc.communicate()
timer.cancel()
return proc.returncode, stdout.decode("utf-8"), stderr.decode("utf-8"), timeout["value"]
timeout
を使った人は誰もいません
timeout 5 ping -c 3 somehost
これはすべてのユースケースでうまくいくわけではありませんが、単純なスクリプトを扱うのであれば、これを破るのは困難です。
Macユーザーのためにhomebrew
を介してcoreutilsのgtimeoutとしても利用可能です。
もう1つの選択肢は、communication()でポーリングする必要がなく、一時的なファイルに書き込んで標準出力のブロックを防ぐことです。他の答えがそうでなかったところ、これは私のために働きました。例えば窓などです。
outFile = tempfile.SpooledTemporaryFile()
errFile = tempfile.SpooledTemporaryFile()
proc = subprocess.Popen(args, stderr=errFile, stdout=outFile, universal_newlines=False)
wait_remaining_sec = timeout
while proc.poll() is None and wait_remaining_sec > 0:
time.sleep(1)
wait_remaining_sec -= 1
if wait_remaining_sec <= 0:
killProc(proc.pid)
raise ProcessIncompleteError(proc, timeout)
# read temp streams from start
outFile.seek(0);
errFile.seek(0);
out = outFile.read()
err = errFile.read()
outFile.close()
errFile.close()
timeout
は現在サポートされています サブプロセスモジュールのcall()
とcommunicate()
により(Python3.3以降):
import subprocess
subprocess.call("command", timeout=20, Shell=True)
これによりコマンドが呼び出され、例外が発生します。
subprocess.TimeoutExpired
20秒経ってもコマンドが終了しない場合.
その後、例外を処理してコードを続行できます。
try:
subprocess.call("command", timeout=20, Shell=True)
except subprocess.TimeoutExpired:
# insert code here
お役に立てれば。
これが私の解決策です、私はThreadとEventを使っていました:
import subprocess
from threading import Thread, Event
def kill_on_timeout(done, timeout, proc):
if not done.wait(timeout):
proc.kill()
def exec_command(command, timeout):
done = Event()
proc = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
watcher = Thread(target=kill_on_timeout, args=(done, timeout, proc))
watcher.daemon = True
watcher.start()
data, stderr = proc.communicate()
done.set()
return data, stderr, proc.returncode
実際には:
In [2]: exec_command(['sleep', '10'], 5)
Out[2]: ('', '', -9)
In [3]: exec_command(['sleep', '10'], 11)
Out[3]: ('', '', 0)
それが言及されていない理由はわかりませんが、Python 3.5以降、新しい subprocess.run
universalコマンド(これはcheck_call
、check_output
を置き換えるためのものです)があり、timeout
パラメーターもあります。
subprocess.run(args、*、stdin =なし、入力=なし、stdout =なし、stderr =なし、シェル= False、cwd =なし、タイムアウト=なし、チェック= False、エンコード=なし、エラー=なし)
Run the command described by args. Wait for command to complete, then return a CompletedProcess instance.
タイムアウトになるとsubprocess.TimeoutExpired
例外が発生します。
私が使用する解決策は、 timelimit をシェルコマンドに付けることです。コマンドが長すぎる場合、timelimitはそれを停止し、Popenはtimelimitによって設定されたリターンコードを持ちます。それが128より大きければ、それは時間制限がプロセスを殺したことを意味します。
タイムアウトと大きな出力(> 64K)を持つ pythonサブプロセスも参照してください
私はjcollado
から私のPythonモジュール easyprocess へのスレッドを使った解決策を追加しました。
インストール:
pip install easyprocess
例:
from easyprocess import Proc
# Shell is not supported!
stdout=Proc('ping localhost').call(timeout=1.5).stdout
print stdout
あなたがpython 2を使っているなら、試してみる
import subprocess32
try:
output = subprocess32.check_output(command, Shell=True, timeout=3)
except subprocess32.TimeoutExpired as e:
print e
* unixで全プロセス実行の機械を理解したら、簡単な解決策が簡単に見つかるでしょう。
この単純な例で、select.select()を使用してタイムアウト可能なcommunic()メソッドを作成する方法を考えてみましょう(現在、ほとんどすべてのサイトで利用可能です)。これはepoll/poll/kqueueで書くこともできますが、select.select()バリアントはあなたにとって良い例です。そしてselect.select()(speedと1024 max fds)の大きな制限はあなたのタスクには適用できません。
これは* nixの下で動作し、スレッドを作成せず、シグナルを使用せず、(mainだけでなく)どのスレッドからも起動でき、そして私のマシン(i5 2.3ghz)の標準出力から250mb/sのデータを読むのに十分速い。
通信の終わりにstdout/stderrに参加することに問題があります。あなたが巨大なプログラム出力を持っているなら、これは大きなメモリ使用量につながるかもしれません。しかし、タイムアウトを短くして、communic()を複数回呼び出すことができます。
class Popen(subprocess.Popen):
def communicate(self, input=None, timeout=None):
if timeout is None:
return subprocess.Popen.communicate(self, input)
if self.stdin:
# Flush stdio buffer, this might block if user
# has been writing to .stdin in an uncontrolled
# fashion.
self.stdin.flush()
if not input:
self.stdin.close()
read_set, write_set = [], []
stdout = stderr = None
if self.stdin and input:
write_set.append(self.stdin)
if self.stdout:
read_set.append(self.stdout)
stdout = []
if self.stderr:
read_set.append(self.stderr)
stderr = []
input_offset = 0
deadline = time.time() + timeout
while read_set or write_set:
try:
rlist, wlist, xlist = select.select(read_set, write_set, [], max(0, deadline - time.time()))
except select.error as ex:
if ex.args[0] == errno.EINTR:
continue
raise
if not (rlist or wlist):
# Just break if timeout
# Since we do not close stdout/stderr/stdin, we can call
# communicate() several times reading data by smaller pieces.
break
if self.stdin in wlist:
chunk = input[input_offset:input_offset + subprocess._PIPE_BUF]
try:
bytes_written = os.write(self.stdin.fileno(), chunk)
except OSError as ex:
if ex.errno == errno.EPIPE:
self.stdin.close()
write_set.remove(self.stdin)
else:
raise
else:
input_offset += bytes_written
if input_offset >= len(input):
self.stdin.close()
write_set.remove(self.stdin)
# Read stdout / stderr by 1024 bytes
for fn, tgt in (
(self.stdout, stdout),
(self.stderr, stderr),
):
if fn in rlist:
data = os.read(fn.fileno(), 1024)
if data == '':
fn.close()
read_set.remove(fn)
tgt.append(data)
if stdout is not None:
stdout = ''.join(stdout)
if stderr is not None:
stderr = ''.join(stderr)
return (stdout, stderr)
Linuxのコマンドtimeout
を前に付けるのは悪い回避策ではありませんし、私にとってはうまくいきました。
cmd = "timeout 20 "+ cmd
subprocess.Popen(cmd.split(), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(output, err) = p.communicate()
私はこれらのうちのいくつかから集めることができるものを実行しました。これはWindowsで動作します。これはコミュニティWikiなので、私も自分のコードを共有します。
class Command(threading.Thread):
def __init__(self, cmd, outFile, errFile, timeout):
threading.Thread.__init__(self)
self.cmd = cmd
self.process = None
self.outFile = outFile
self.errFile = errFile
self.timed_out = False
self.timeout = timeout
def run(self):
self.process = subprocess.Popen(self.cmd, stdout = self.outFile, \
stderr = self.errFile)
while (self.process.poll() is None and self.timeout > 0):
time.sleep(1)
self.timeout -= 1
if not self.timeout > 0:
self.process.terminate()
self.timed_out = True
else:
self.timed_out = False
それから他のクラスやファイルから:
outFile = tempfile.SpooledTemporaryFile()
errFile = tempfile.SpooledTemporaryFile()
executor = command.Command(c, outFile, errFile, timeout)
executor.daemon = True
executor.start()
executor.join()
if executor.timed_out:
out = 'timed out'
else:
outFile.seek(0)
errFile.seek(0)
out = outFile.read()
err = errFile.read()
outFile.close()
errFile.close()
あまり詳しく見ていませんが、これは デコレータ ActiveStateで見つけたもので、このようなことには非常に便利です。 subprocess.Popen(..., close_fds=True)
と一緒に、少なくとも私はPythonでシェルスクリプトを書く準備ができています。
マルチスレッドのサブプロセスが指定されたタイムアウト時間より長くかかる場合、そのサブプロセスを終了したいという問題がありました。タイムアウトをPopen()
に設定したいのですが、うまくいきませんでした。それから、私はPopen().wait()
がcall()
と等しいことに気付いたので、.wait(timeout=xxx)
メソッド内でタイムアウトを設定するというアイディアを得ました。したがって、私はそれをこのように解決しました:
import os
import sys
import signal
import subprocess
from multiprocessing import Pool
cores_for_parallelization = 4
timeout_time = 15 # seconds
def main():
jobs = [...YOUR_JOB_LIST...]
with Pool(cores_for_parallelization) as p:
p.map(run_parallel_jobs, jobs)
def run_parallel_jobs(args):
# Define the arguments including the paths
initial_terminal_command = 'C:\\Python34\\python.exe' # Python executable
function_to_start = 'C:\\temp\\xyz.py' # The multithreading script
final_list = [initial_terminal_command, function_to_start]
final_list.extend(args)
# Start the subprocess and determine the process PID
subp = subprocess.Popen(final_list) # starts the process
pid = subp.pid
# Wait until the return code returns from the function by considering the timeout.
# If not, terminate the process.
try:
returncode = subp.wait(timeout=timeout_time) # should be zero if accomplished
except subprocess.TimeoutExpired:
# Distinguish between Linux and Windows and terminate the process if
# the timeout has been expired
if sys.platform == 'linux2':
os.kill(pid, signal.SIGTERM)
Elif sys.platform == 'win32':
subp.terminate()
if __== '__main__':
main()
Popenクラスをサブクラス化し、いくつかの単純なメソッドデコレータでそれを拡張するという考えがあります。それをExpirablePopenと呼びましょう。
from logging import error
from subprocess import Popen
from threading import Event
from threading import Thread
class ExpirablePopen(Popen):
def __init__(self, *args, **kwargs):
self.timeout = kwargs.pop('timeout', 0)
self.timer = None
self.done = Event()
Popen.__init__(self, *args, **kwargs)
def __tkill(self):
timeout = self.timeout
if not self.done.wait(timeout):
error('Terminating process {} by timeout of {} secs.'.format(self.pid, timeout))
self.kill()
def expirable(func):
def wrapper(self, *args, **kwargs):
# zero timeout means call of parent method
if self.timeout == 0:
return func(self, *args, **kwargs)
# if timer is None, need to start it
if self.timer is None:
self.timer = thr = Thread(target=self.__tkill)
thr.daemon = True
thr.start()
result = func(self, *args, **kwargs)
self.done.set()
return result
return wrapper
wait = expirable(Popen.wait)
communicate = expirable(Popen.communicate)
if __== '__main__':
from subprocess import PIPE
print ExpirablePopen('ssh -T [email protected]', stdout=PIPE, timeout=1).communicate()
select
を使ってこれを行うことができます。
import subprocess
from datetime import datetime
from select import select
def call_with_timeout(cmd, timeout):
started = datetime.now()
sp = subprocess.Popen(cmd, stdout=subprocess.PIPE)
while True:
p = select([sp.stdout], [], [], timeout)
if p[0]:
p[0][0].read()
ret = sp.poll()
if ret is not None:
return ret
if (datetime.now()-started).total_seconds() > timeout:
sp.kill()
return None
私は killableprocess をWindows、LinuxそしてMacでうまく使っています。 Cygwin Pythonを使用している場合、 OSAFのバージョンのkillableprocess が必要です。それ以外の場合、ネイティブのWindowsプロセスは強制終了されません。
このソリューションは、Shell = Trueの場合にプロセスツリーを強制終了し、プロセスにパラメータを渡し(またはしないで)、タイムアウトしてコールバックの標準出力、標準エラー出力、およびプロセス出力を取得します(kill_proc_treeにpsutilを使用)。これはjcolladoを含むSOに投稿されたいくつかの解決策に基づいていました。 Ansonとjradiceによるコメントに応えてjcolladoの回答に投稿する。 Windows Srvr 2012およびUbuntu 14.04でテスト済み。 Ubuntuでは、parent.children(...)呼び出しをparent.get_children(...)に変更する必要があります。
def kill_proc_tree(pid, including_parent=True):
parent = psutil.Process(pid)
children = parent.children(recursive=True)
for child in children:
child.kill()
psutil.wait_procs(children, timeout=5)
if including_parent:
parent.kill()
parent.wait(5)
def run_with_timeout(cmd, current_dir, cmd_parms, timeout):
def target():
process = subprocess.Popen(cmd, cwd=current_dir, Shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE)
# wait for the process to terminate
if (cmd_parms == ""):
out, err = process.communicate()
else:
out, err = process.communicate(cmd_parms)
errcode = process.returncode
thread = Thread(target=target)
thread.start()
thread.join(timeout)
if thread.is_alive():
me = os.getpid()
kill_proc_tree(me, including_parent=False)
thread.join()
python 2.6以降の場合は、geventを使用してください。
from gevent.subprocess import Popen, PIPE, STDOUT
def call_sys(cmd, timeout):
p= Popen(cmd, Shell=True, stdout=PIPE)
output, _ = p.communicate(timeout=timeout)
assert p.returncode == 0, p. returncode
return output
call_sys('./t.sh', 2)
# t.sh example
sleep 5
echo done
exit 1
https://pypi.python.org/pypi/python-subprocess2 サブプロセスモジュールを拡張したもので、一定の時間待つことができ、そうでない場合は終了します。
そのため、プロセスが終了するまで最大10秒待つために、そうでなければkill:
pipe = subprocess.Popen('...')
timeout = 10
results = pipe.waitOrTerminate(timeout)
これはwindowsとunixの両方と互換性があります。 "results"は辞書で、 "returnCode"はアプリの返り値です(またはkillする必要がある場合は "None")と "actionTaken"が含まれます。プロセスが正常に完了した場合は "SUBPROCESS2_PROCESS_COMPLETED"、実行されたアクションに応じて "SUBPROCESS2_PROCESS_TERMINATED"とSUBPROCESS2_PROCESS_KILLEDのマスク(詳細はドキュメントを参照)
残念ながら、私は雇用主によるソースコードの公開に関する非常に厳格な方針に縛られているので、実際のコードを提供することはできません。しかし、私の好みでは、最善の解決策は、無期限に待機する代わりにPopen.wait()
をポーリングするようにオーバーライドし、Popen.__init__
をタイムアウトパラメータを受け入れるようにオーバーライドするサブクラスを作成することです。これを行うと、Popen
を含め、他のすべてのwait
メソッド(communicate
を呼び出す)は期待どおりに機能します。
python 2.7
import time
import subprocess
def run_command(cmd, timeout=0):
start_time = time.time()
df = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
while timeout and df.poll() == None:
if time.time()-start_time >= timeout:
df.kill()
return -1, ""
output = '\n'.join(df.communicate()).strip()
return df.returncode, output