这是Python代码,用于运行任意命令返回其stdout
数据,或者在非零退出代码上引发异常:
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程序中实现超时的最简单方式是什么?
答案 0 :(得分:195)
我对低级细节知之甚少;但是,考虑到这一点 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),而在第二个完成 进程终止(返回代码-15)。
我没有在Windows中测试过;但是,除了更新示例 命令,我认为它应该工作,因为我还没有找到 记录任何说明thread.join或process.terminate的内容 不受支持。
答案 1 :(得分:137)
在Python 3.3 +中:
from subprocess import STDOUT, check_output
output = check_output(cmd, stderr=STDOUT, timeout=seconds)
output
是一个字节字符串,包含命令的合并stdout,stderr数据。
与CalledProcessError
方法不同,此代码在问题文本中指定的非零退出状态上引发proc.communicate()
。
我已删除shell=True
,因为它经常被不必要地使用。如果cmd
确实需要,您可以随时添加它。如果添加shell=True
,即子进程生成自己的后代; check_output()
可以在超时指示的时间之后返回,请参阅Subprocess timeout failure。
通过3.2+子进程模块的subprocess32
后端,Python 2.x上提供了超时功能。
答案 2 :(得分:109)
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
答案 3 :(得分:80)
如果你在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
答案 4 :(得分:43)
答案 5 :(得分:16)
我修改了 sussudio 的答案。现在函数返回:(returncode
,stdout
,stderr
,timeout
) - stdout
和stderr
被解码为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"]
答案 6 :(得分:14)
惊讶没有人提到使用timeout
timeout 5 ping -c 3 somehost
显然,这对每个用例的工作都没有好处,但是如果你处理一个简单的脚本,这很难被击败。
对于mac用户,还可以通过homebrew
在coreutils中作为gtimeout使用。
答案 7 :(得分:10)
另一种选择是写入临时文件以防止stdout阻塞而不需要使用communic()进行轮询。这对我有用,而其他答案没有;例如在Windows上。
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()
答案 8 :(得分:9)
call()
和communicate()
的 timeout
is now supported(从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
希望这有帮助。
答案 9 :(得分:5)
这是我的解决方案,我使用的是线程和事件:
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)
答案 10 :(得分:5)
我不知道为什么它没有被提及但是从Python 3.5开始,有一个新的subprocess.run
通用命令(用于替换check_call
,{{ 1}} ...)并且还有check_output
参数。
subprocess.run(args,*,stdin = None,input = None,stdout = None,stderr = None,shell = False,cwd = None,timeout = None,check = False,encoding = None,errors = None )
timeout
超时过期时会引发Run the command described by args. Wait for command to complete, then return a CompletedProcess instance.
异常。
答案 11 :(得分:4)
我添加了从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
答案 12 :(得分:4)
我使用的解决方案是在shell命令前加上timelimit。如果命令花费太长时间,则timelimit将停止它并且Popen将具有由timelimit设置的返回码。如果是> 128,这意味着timelimit杀死了这个过程。
答案 13 :(得分:3)
如果您使用的是python 2,请尝试一下
import subprocess32
try:
output = subprocess32.check_output(command, shell=True, timeout=3)
except subprocess32.TimeoutExpired as e:
print e
答案 14 :(得分:2)
一旦您了解* unix中的完整流程运行机制,您将很容易找到更简单的解决方案:
考虑这个简单的例子如何使用select.select()实现超时的communic()方法(现在在* nix上几乎每个都可用)。这也可以用epoll / poll / kqueue编写,但select.select()变体可能是一个很好的例子。 select.select()(速度和1024最大fds)的主要限制不适用于您的任务。
这可以在* nix下工作,不会创建线程,不使用信号,可以从任何线程(不仅是主线程)推出,并且快速从我的机器上的stdout读取250mb / s的数据(i5 2.3ghz )。
在沟通结束时加入stdout / stderr时出现问题。如果你有巨大的程序输出,这可能会导致大量的内存使用。但是你可以用较小的超时调用communication()几次。
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)
答案 15 :(得分:2)
我已经实现了我可以从其中一些中收集到的东西。这适用于Windows,因为这是一个社区维基,我想我也会分享我的代码:
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()
答案 16 :(得分:1)
有一个想法是继承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 __name__ == '__main__':
from subprocess import PIPE
print ExpirablePopen('ssh -T git@bitbucket.org', stdout=PIPE, timeout=1).communicate()
答案 17 :(得分:1)
预装Linux命令timeout
并不是一个糟糕的解决方法,它对我有用。
cmd = "timeout 20 "+ cmd
subprocess.Popen(cmd.split(), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(output, err) = p.communicate()
答案 18 :(得分:1)
您可以使用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
答案 19 :(得分:1)
我遇到的问题是,如果花费超过给定的超时长度,我想终止多线程子进程。我想在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 __name__ == '__main__':
main()
答案 20 :(得分:1)
虽然我没有广泛地看过它,但我在ActiveState中找到的decorator似乎对这类事情非常有用。与subprocess.Popen(..., close_fds=True)
一起,至少我已经准备好在Python中使用shell脚本了。
答案 21 :(得分:1)
我在Windows,Linux和Mac上成功使用了killableprocess。如果您使用Cygwin Python,则需要OSAF's version of killableprocess,否则本机Windows进程不会被杀死。
答案 22 :(得分:0)
对于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
答案 23 :(得分:0)
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
答案 24 :(得分:0)
在Python 3.7.8中测试超时后捕获的输出示例:
try:
return subprocess.run(command, shell=True, capture_output=True, timeout=20, cwd=cwd, universal_newlines=True)
except subprocess.TimeoutExpired as e:
print(e.output.decode(encoding="utf-8", errors="ignore"))
assert False;
异常subprocess.TimeoutExpired具有输出和其他成员:
cmd-用于生成子进程的命令。
超时-超时(以秒为单位)。
output-子进程的输出,如果该子进程被run()或 check_output()。否则,没有。
stdout-输出别名,用于与stderr对称。
stderr-子进程的Stderr输出(如果被捕获) 跑()。否则,没有。
更多信息:https://docs.python.org/3/library/subprocess.html#subprocess.TimeoutExpired
答案 25 :(得分:0)
仅适用于 Linux
的延迟答案,但如果有人想使用 subprocess.getstatusoutput()
,而超时参数不可用,您可以在命令开头使用 built-in Linux timeout ,即:
import subprocess
timeout = 25 # seconds
cmd = f"timeout --preserve-status --foreground {timeout} ping duckgo.com"
exit_c, out = subprocess.getstatusoutput(cmd)
if (exit_c == 0):
print("success")
else:
print("Error: ", out)
timeout
参数:
--preserve-status
: Preserving the Exit Status--foreground
: Running in Foreground25
: 以秒为单位的超时值答案 26 :(得分:0)
此解决方案在shell = True的情况下终止进程树,将参数传递给进程(或不传递),超时并获取回调的stdout,stderr和process输出(它使用psutil作为kill_proc_tree) 。这是基于SO中发布的几个解决方案,包括jcollado的。发表回应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()
答案 27 :(得分:0)
https://pypi.python.org/pypi/python-subprocess2提供了子进程模块的扩展,允许您等待一段时间,否则终止。
所以,要等待最多10秒才能终止进程,否则终止:
validates :email, length: { maximum: 255 }, format: { with: VALID_EMAIL_REGEX }, uniqueness: { case_sensitive: false }
这与windows和unix兼容。 “results”是一个字典,它包含“returnCode”,它是应用程序的返回(如果必须被杀死则为None),以及“actionTaken”。如果进程正常完成,则为“SUBPROCESS2_PROCESS_COMPLETED”,或者根据所采取的操作设置“SUBPROCESS2_PROCESS_TERMINATED”和SUBPROCESS2_PROCESS_KILLED的掩码(参见文档以获取完整详细信息)
答案 28 :(得分:0)
不幸的是,我受到雇主披露源代码的严格政策约束,所以我无法提供实际的代码。但根据我的喜好,最好的解决方案是创建一个重写Popen.wait()
的子类来轮询而不是无限期地等待,并Popen.__init__
接受一个超时参数。完成此操作后,所有其他Popen
方法(调用wait
)将按预期工作,包括communicate
。
答案 29 :(得分:-1)
import subprocess, optparse, os, sys, re, datetime, threading, time, glob, shutil, xml.dom.minidom, traceback
class OutputManager:
def __init__(self, filename, mode, console, logonly):
self.con = console
self.logtoconsole = True
self.logtofile = False
if filename:
try:
self.f = open(filename, mode)
self.logtofile = True
if logonly == True:
self.logtoconsole = False
except IOError:
print (sys.exc_value)
print ("Switching to console only output...\n")
self.logtofile = False
self.logtoconsole = True
def write(self, data):
if self.logtoconsole == True:
self.con.write(data)
if self.logtofile == True:
self.f.write(data)
sys.stdout.flush()
def getTimeString():
return time.strftime("%Y-%m-%d", time.gmtime())
def runCommand(command):
'''
Execute a command in new thread and return the
stdout and stderr content of it.
'''
try:
Output = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True).communicate()[0]
except Exception as e:
print ("runCommand failed :%s" % (command))
print (str(e))
sys.stdout.flush()
return None
return Output
def GetOs():
Os = ""
if sys.platform.startswith('win32'):
Os = "win"
elif sys.platform.startswith('linux'):
Os = "linux"
elif sys.platform.startswith('darwin'):
Os = "mac"
return Os
def check_output(*popenargs, **kwargs):
try:
if 'stdout' in kwargs:
raise ValueError('stdout argument not allowed, it will be overridden.')
# Get start time.
startTime = datetime.datetime.now()
timeoutValue=3600
cmd = popenargs[0]
if sys.platform.startswith('win32'):
process = subprocess.Popen( cmd, stdout=subprocess.PIPE, shell=True)
elif sys.platform.startswith('linux'):
process = subprocess.Popen( cmd , stdout=subprocess.PIPE, shell=True )
elif sys.platform.startswith('darwin'):
process = subprocess.Popen( cmd , stdout=subprocess.PIPE, shell=True )
stdoutdata, stderrdata = process.communicate( timeout = timeoutValue )
retcode = process.poll()
####################################
# Catch crash error and log it.
####################################
OutputHandle = None
try:
if retcode >= 1:
OutputHandle = OutputManager( 'CrashJob_' + getTimeString() + '.txt', 'a+', sys.stdout, False)
OutputHandle.write( cmd )
print (stdoutdata)
print (stderrdata)
sys.stdout.flush()
except Exception as e:
print (str(e))
except subprocess.TimeoutExpired:
####################################
# Catch time out error and log it.
####################################
Os = GetOs()
if Os == 'win':
killCmd = "taskkill /FI \"IMAGENAME eq {0}\" /T /F"
elif Os == 'linux':
killCmd = "pkill {0)"
elif Os == 'mac':
# Linux, Mac OS
killCmd = "killall -KILL {0}"
runCommand(killCmd.format("java"))
runCommand(killCmd.format("YouApp"))
OutputHandle = None
try:
OutputHandle = OutputManager( 'KillJob_' + getTimeString() + '.txt', 'a+', sys.stdout, False)
OutputHandle.write( cmd )
except Exception as e:
print (str(e))
except Exception as e:
for frame in traceback.extract_tb(sys.exc_info()[2]):
fname,lineno,fn,text = frame
print "Error in %s on line %d" % (fname, lineno)
答案 30 :(得分:-2)
只是想写一些更简单的东西。
#!/usr/bin/python
from subprocess import Popen, PIPE
import datetime
import time
popen = Popen(["/bin/sleep", "10"]);
pid = popen.pid
sttime = time.time();
waittime = 3
print "Start time %s"%(sttime)
while True:
popen.poll();
time.sleep(1)
rcode = popen.returncode
now = time.time();
if [ rcode is None ] and [ now > (sttime + waittime) ] :
print "Killing it now"
popen.kill()