J'ai un petit problème que je ne sais pas trop comment résoudre. Voici un exemple minimal:
scan_process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
while(some_criterium):
line = scan_process.stdout.readline()
some_criterium = do_something(line)
scan_process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
while(some_criterium):
line = scan_process.stdout.readline()
if nothing_happens_after_10s:
break
else:
some_criterium = do_something(line)
Je lis une ligne d'un sous-processus et j'en fais quelque chose. Ce que je veux, c'est quitter si aucune ligne n'est arrivée après un intervalle de temps fixe. Des recommandations?
Merci pour toutes les réponses! J'ai trouvé un moyen de résoudre mon problème en utilisant simplement select.poll pour jeter un œil dans stdout.
import select
...
scan_process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
poll_obj = select.poll()
poll_obj.register(scan_process.stdout, select.POLLIN)
while(some_criterium and not time_limit):
poll_result = poll_obj.poll(0)
if poll_result:
line = scan_process.stdout.readline()
some_criterium = do_something(line)
update(time_limit)
Voici une solution portable qui applique le délai d'attente pour la lecture d'une seule ligne en utilisant asyncio
:
#!/usr/bin/env python3
import asyncio
import sys
from asyncio.subprocess import PIPE, STDOUT
async def run_command(*args, timeout=None):
# start child process
# NOTE: universal_newlines parameter is not supported
process = await asyncio.create_subprocess_exec(*args,
stdout=PIPE, stderr=STDOUT)
# read line (sequence of bytes ending with b'\n') asynchronously
while True:
try:
line = await asyncio.wait_for(process.stdout.readline(), timeout)
except asyncio.TimeoutError:
pass
else:
if not line: # EOF
break
Elif do_something(line):
continue # while some criterium is satisfied
process.kill() # timeout or some criterium is not satisfied
break
return await process.wait() # wait for the child process to exit
if sys.platform == "win32":
loop = asyncio.ProactorEventLoop() # for subprocess' pipes on Windows
asyncio.set_event_loop(loop)
else:
loop = asyncio.get_event_loop()
returncode = loop.run_until_complete(run_command("cmd", "arg 1", "arg 2",
timeout=10))
loop.close()
J'ai utilisé quelque chose d'un peu plus général dans python (IIRC aussi reconstitué à partir de SO questions, mais je ne me souviens pas lesquelles)).
import thread
from threading import Timer
def run_with_timeout(timeout, default, f, *args, **kwargs):
if not timeout:
return f(*args, **kwargs)
try:
timeout_timer = Timer(timeout, thread.interrupt_main)
timeout_timer.start()
result = f(*args, **kwargs)
return result
except KeyboardInterrupt:
return default
finally:
timeout_timer.cancel()
Soyez averti, cependant, cela utilise une interruption pour arrêter la fonction que vous lui donnez. Cela peut ne pas être une bonne idée pour toutes les fonctions et cela vous empêche également de fermer le programme avec ctrl + c pendant le timeout (c'est-à-dire que ctrl + c sera traité comme un timeout) Vous pouvez utiliser ceci comme un appel comme:
scan_process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
while(some_criterium):
line = run_with_timeout(timeout, None, scan_process.stdout.readline)
if line is None:
break
else:
some_criterium = do_something(line)
Cela pourrait être un peu exagéré, cependant. Je soupçonne qu'il existe une option plus simple pour votre cas que je ne connais pas.
Une solution portable consiste à utiliser un thread pour tuer le processus enfant si la lecture d'une ligne prend trop de temps:
#!/usr/bin/env python3
from subprocess import Popen, PIPE, STDOUT
timeout = 10
with Popen(command, stdout=PIPE, stderr=STDOUT,
universal_newlines=True) as process: # text mode
# kill process in timeout seconds unless the timer is restarted
watchdog = WatchdogTimer(timeout, callback=process.kill, daemon=True)
watchdog.start()
for line in process.stdout:
# don't invoke the watcthdog callback if do_something() takes too long
with watchdog.blocked:
if not do_something(line): # some criterium is not satisfied
process.kill()
break
watchdog.restart() # restart timer just before reading the next line
watchdog.cancel()
où WatchdogTimer
classe est comme threading.Timer
pouvant être redémarré et/ou bloqué:
from threading import Event, Lock, Thread
from subprocess import Popen, PIPE, STDOUT
from time import monotonic # use time.time or monotonic.monotonic on Python 2
class WatchdogTimer(Thread):
"""Run *callback* in *timeout* seconds unless the timer is restarted."""
def __init__(self, timeout, callback, *args, timer=monotonic, **kwargs):
super().__init__(**kwargs)
self.timeout = timeout
self.callback = callback
self.args = args
self.timer = timer
self.cancelled = Event()
self.blocked = Lock()
def run(self):
self.restart() # don't start timer until `.start()` is called
# wait until timeout happens or the timer is canceled
while not self.cancelled.wait(self.deadline - self.timer()):
# don't test the timeout while something else holds the lock
# allow the timer to be restarted while blocked
with self.blocked:
if self.deadline <= self.timer() and not self.cancelled.is_set():
return self.callback(*self.args) # on timeout
def restart(self):
"""Restart the watchdog timer."""
self.deadline = self.timer() + self.timeout
def cancel(self):
self.cancelled.set()
Essayez d'utiliser signal.alarm:
#timeout.py
import signal,sys
def timeout(sig,frm):
print "This is taking too long..."
sys.exit(1)
signal.signal(signal.SIGALRM, timeout)
signal.alarm(10)
byte=0
while 'IT' not in open('/dev/urandom').read(2):
byte+=2
print "I got IT in %s byte(s)!" % byte
Quelques essais pour montrer que cela fonctionne:
$ python timeout.py
This is taking too long...
$ python timeout.py
I got IT in 4672 byte(s)!
Pour un exemple plus détaillé, voir pGuides .
Dans Python 3, une option de temporisation a été ajoutée au module de sous-processus. À l'aide d'une structure comme
try:
o, e = process.communicate(timeout=10)
except TimeoutExpired:
process.kill()
o, e = process.communicate()
analyze(o)
serait une bonne solution.
Étant donné que la sortie devrait contenir un nouveau caractère de ligne, il est sûr de supposer qu'il s'agit de texte (comme dans imprimable, lisible), auquel cas universal_newlines=True
le drapeau est fortement recommandé.
Si Python2 est un must, veuillez utiliser https://pypi.python.org/pypi/subprocess32/ (backport)
Pour un python pur Python 2, regardez tilisation du module 'sous-processus' avec timeout .
pendant que votre solution (de Tom) fonctionne, l'utilisation de select()
dans l'idiome C
est plus compacte. c'est l'équivalent de votre réponse
from select import select
scan_process = subprocess.Popen(command,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
bufsize=1) # line buffered
while some_criterium and not time_limit:
poll_result = select([scan_process.stdout], [], [], time_limit)[0]
le reste est le même.
voir pydoc select.select
.
[Remarque: ceci est spécifique à Unix, comme le sont certaines des autres réponses.]
[Remarque 2: modifié pour ajouter une mise en mémoire tampon de ligne conformément à la demande OP]
[Remarque 3: la mise en mémoire tampon de la ligne peut ne pas être fiable dans toutes les circonstances, ce qui entraîne un blocage de readline ()]