Verwenden des Moduls 'Unterprozess' mit Zeitüberschreitung


325

Hier ist der Python-Code, mit dem Sie einen beliebigen Befehl ausführen können, der seine stdoutDaten zurückgibt, oder eine Ausnahme für Exit-Codes ungleich Null auslösen:

proc = subprocess.Popen(
    cmd,
    stderr=subprocess.STDOUT,  # Merge stdout and stderr
    stdout=subprocess.PIPE,
    shell=True)

communicate wird verwendet, um auf das Beenden des Prozesses zu warten:

stdoutdata, stderrdata = proc.communicate()

Das subprocessModul unterstützt kein Timeout - die Fähigkeit, einen Prozess abzubrechen, der länger als X Sekunden ausgeführt wird - kann daher communicateewig dauern.

Was ist der einfachste Weg, um Zeitüberschreitungen in einem Python-Programm zu implementieren, das unter Windows und Linux ausgeführt werden soll?


2
Ein verwandter Python Issue Tracker Eintrag: bugs.python.org/issue5673
Sridhar Ratnakumar

10
Verwenden Sie pypi.python.org/pypi/subprocess32 für Python2.x. Es ist ein Backport von Python 3.x. Es hat das Timeout-Argument für call () und wait ().
Guettli

1
pypi.python.org/pypi/subprocess32 funktioniert nicht unter Windows :(
adrianX

Antworten:


169

In Python 3.3+:

from subprocess import STDOUT, check_output

output = check_output(cmd, stderr=STDOUT, timeout=seconds)

output ist eine Byte-Zeichenfolge, die die zusammengeführten stdout- und stderr-Daten des Befehls enthält.

check_outputErhöht den CalledProcessErrorExit-Status ungleich Null, wie im Text der Frage angegeben, im Gegensatz zur proc.communicate()Methode.

Ich habe entfernt, shell=Trueweil es oft unnötig verwendet wird. Sie können es jederzeit wieder hinzufügen, wenn dies cmdtatsächlich erforderlich ist. Wenn Sie hinzufügen, shell=Truedh wenn der untergeordnete Prozess seine eigenen Nachkommen erzeugt; check_output()kann viel später als im Zeitlimit angegeben zurückgegeben werden, siehe Fehler beim Zeitlimit für Unterprozesse .

Die Timeout-Funktion ist in Python 2.x über den subprocess32Backport des Subprozessmoduls 3.2+ verfügbar .


17
In der Tat gibt es Unterstützung für das Subprozess-Timeout im Subprozess32-Backport, den ich für die Verwendung unter Python 2 verwalte. Pypi.python.org/pypi/subprocess32
gps

8
@gps Sridhar fragte nach einer plattformübergreifenden Lösung, während Ihr Backport nur POSIX unterstützt: Als ich es ausprobierte, beschwerte sich MSVC (erwartet) über das Fehlen von unistd.h :)
Shmil The Cat

Wenn Sie die Ausgabe nicht benötigen, können Sie einfach die Datei subprocess.call verwenden.
Kyle Gibson

Verwenden Sie seit Python3.5 subprocess.run () mit capture_output = True und verwenden Sie den Codierungsparameter, um eine nützliche Ausgabe zu erhalten.
MKesper

1
@MKesper: 1- check_output()ist der bevorzugte Weg, um eine Ausgabe zu erhalten (es gibt die Ausgabe direkt zurück, ignoriert keine Fehler, es ist seit Ewigkeiten verfügbar). 2- run()ist flexibler, run()ignoriert jedoch standardmäßig Fehler und erfordert zusätzliche Schritte, um die Ausgabe zu erhalten. 3- check_output()ist in Bezug auf implementiertrun() und akzeptiert daher die meisten der gleichen Argumente. 4-nit: capture_outputist verfügbar seit 3.7, nicht 3.5
jfs

205

Ich weiß nicht viel über die Details auf niedriger Ebene. Angesichts der Tatsache, dass die API in Python 2.6 die Möglichkeit bietet, auf Threads zu warten und Prozesse zu beenden, können Sie den Prozess in einem separaten Thread ausführen.

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)

Die Ausgabe dieses Snippets in meinem Computer ist:

Thread started
Process started
Process finished
Thread finished
0
Thread started
Process started
Terminating process
Thread finished
-15

wo zu sehen ist, dass in der ersten Ausführung der Prozess korrekt beendet wurde (Rückkehrcode 0), während in der zweiten der Prozess beendet wurde (Rückkehrcode -15).

Ich habe nicht in Windows getestet. Abgesehen von der Aktualisierung des Beispielbefehls sollte dies funktionieren, da ich in der Dokumentation nichts gefunden habe, was besagt, dass thread.join oder process.terminate nicht unterstützt wird.


16
+1 Um plattformunabhängig zu sein. Ich habe dies sowohl unter Linux als auch unter Windows 7 (Cygwin und einfaches Windows Python) ausgeführt - funktioniert in allen drei Fällen wie erwartet.
Phooji

7
Ich habe Ihren Code ein wenig geändert, um native Popen-Wargs übergeben und auf den Punkt bringen zu können. Es ist jetzt einsatzbereit. gist.github.com/1306188
Kirpit

2
Für jeden, der das Problem @redice hatte, kann diese Frage hilfreich sein. Kurz gesagt, wenn Sie shell = True verwenden, wird die Shell zum untergeordneten Prozess, der beendet wird, und ihr Befehl (untergeordnetes Element des untergeordneten Prozesses) lebt weiter.
Anson

6
Diese Antwort bietet nicht die gleiche Funktionalität wie das Original, da sie nicht stdout zurückgibt.
Stephenbez

2
thread.is_alive kann zu einer Racebedingung führen. Siehe ostricher.com/2015/01/python-subprocess-with-timeout
ChaimKut

132

Die Antwort von jcollado kann mithilfe der threading.Timer- Klasse vereinfacht werden :

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

11
+1 für einfache tragbare Lösung. Sie brauchen nicht lambda:t = Timer(timeout, proc.kill)
jfs

3
+1 Dies sollte die akzeptierte Antwort sein, da nicht geändert werden muss, wie der Prozess gestartet wird.
Dave Branton

1
Warum braucht es das Lambda? Könnte die gebundene Methode p.kill nicht ohne das dortige Lambda verwendet werden?
Danny Staple

//, Wären Sie bereit, ein Beispiel für die Verwendung dieses Beispiels aufzunehmen?
Nathan Basanese

1
@tuk timer.isAlive()vor timer.cancel()bedeutet, dass es normal endete
Charles

83

Wenn Sie unter Unix sind,

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

3
Nun, ich interessiere mich für eine plattformübergreifende Lösung, die zumindest unter Win / Linux / Mac funktioniert.
Sridhar Ratnakumar

1
Ich mag diesen Unix-basierten Ansatz. Idealerweise würde man dies mit einem Windows-spezifischen Ansatz kombinieren (unter Verwendung von CreateProcess und Jobs). Derzeit ist die folgende Lösung jedoch einfach, leicht und funktioniert bisher.
Sridhar Ratnakumar

3
Ich habe eine tragbare Lösung hinzugefügt, siehe meine Antwort
Flybywire

4
Diese Lösung würde nur funktionieren, wenn signal.signal (signal.SIGALARM, alarm_handler) vom Hauptthread aufgerufen wird. Siehe die Dokumentation für signal
volatilevoid

Leider habe ich festgestellt, dass die Verwendung von Signalen und Alarmen beim Ausführen (unter Linux) im Kontext eines Apache-Moduls (wie mod_python, mod_perl oder mod_php) nicht zulässig ist (vermutlich, weil sie die IPC-Logik von Apache stören). Um das Ziel der Zeitüberschreitung eines Befehls zu erreichen, musste ich "Elternschleifen" schreiben, die einen untergeordneten Prozess starten, und dann in einer "Schlaf" -Schleife sitzen und die Uhr beobachten (und möglicherweise auch die Ausgabe des Kindes überwachen).
Peter

44

Hier ist Alex Martellis Lösung als Modul mit ordnungsgemäßem Prozessabbruch. Die anderen Ansätze funktionieren nicht, da sie nicht proc.communicate () verwenden. Wenn Sie also einen Prozess haben, der viel Ausgabe erzeugt, füllt er seinen Ausgabepuffer und blockiert dann, bis Sie etwas daraus lesen.

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 __name__ == '__main__':
    print run('find /', shell = True, timeout = 3)
    print run('find', shell = True)

3
Dies funktioniert unter Windows nicht und die Reihenfolge der Funktionen ist umgekehrt.
Hamish Grubijan

3
Dies führt manchmal zu Ausnahmen, wenn sich ein anderer Handler bei SIGALARM registriert und den Prozess abbricht, bevor dieser zum "Kill" führt. Übrigens, tolles Rezept! Ich habe dies verwendet, um bisher 50.000 fehlerhafte Prozesse zu starten, ohne den Handling Wrapper einzufrieren oder zum Absturz zu bringen.
Jaroslaw Bulatow

Wie kann dies geändert werden, um in einer Thread-Anwendung ausgeführt zu werden? Ich versuche es aus ValueError: signal only works in main thread
Arbeitsthreads heraus

@ Jaroslaw Bulatow Danke für die Info. Was war die Problemumgehung, die Sie hinzugefügt haben, um das erwähnte Problem zu lösen?
Jpswain

1
Gerade den Block "try; catch" hinzugefügt, der sich im Code befindet. Übrigens stellte sich heraus, dass dies auf lange Sicht Probleme bereitete, da Sie nur einen SIGALARM-Handler festlegen können und andere Prozesse ihn zurücksetzen können. Eine Lösung hierfür finden Sie hier - stackoverflow.com/questions/6553423/…
Yaroslav Bulatov

18

Ich habe die Sussudio- Antwort geändert . Nun Funktion gibt: ( returncode, stdout, stderr, timeout) - stdoutund stderrdecodiert , um utf-8 - String

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"]

18

überrascht niemand erwähnt mit timeout

timeout 5 ping -c 3 somehost

Dies ist natürlich nicht für jeden Anwendungsfall geeignet, aber wenn Sie mit einem einfachen Skript arbeiten, ist dies schwer zu übertreffen.

Auch als Gtimeout in Coreutils über homebrewfür Mac-Benutzer verfügbar .


1
du meinst : proc = subprocess.Popen(['/usr/bin/timeout', str(timeout)] + cmd, ...). Gibt es timeoutunter Windows einen Befehl, wenn OP danach fragt?
JFS

In Windows kann eine Anwendung wie git bash verwendet werden , die Bash-Dienstprogramme in Windows ermöglicht.
Kaushik Acharya

@KaushikAcharya Selbst wenn Sie Git Bash verwenden, wird Python beim Aufrufen eines Unterprozesses unter Windows ausgeführt, daher funktioniert diese Umgehung nicht.
Naman Chikara

16

timeoutwird jetzt von call()und communicate()im Unterprozessmodul (ab Python3.3) unterstützt:

import subprocess

subprocess.call("command", timeout=20, shell=True)

Dadurch wird der Befehl aufgerufen und die Ausnahme ausgelöst

subprocess.TimeoutExpired

wenn der Befehl nach 20 Sekunden nicht beendet wird.

Sie können dann die Ausnahme behandeln, um Ihren Code fortzusetzen, etwa:

try:
    subprocess.call("command", timeout=20, shell=True)
except subprocess.TimeoutExpired:
    # insert code here

Hoffe das hilft.


Es gibt eine Antwort, die den timeoutParameter erwähnt . Noch einmal zu erwähnen würde nicht schaden.
JFS

// Ich denke, OP sucht nach einer Lösung für das ältere Python.
Nathan Basanese

11

Eine andere Möglichkeit besteht darin, in eine temporäre Datei zu schreiben, um die Blockierung von stdout zu verhindern, anstatt mit communic () abfragen zu müssen. Dies funktionierte für mich, wo die anderen Antworten nicht funktionierten; zum Beispiel unter 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()

Scheint unvollständig - was ist tempfile?
Spiderplant0

Fügen Sie "import tempfile", "import time" und "shell = True" in den Aufruf "Popen" ein (Vorsicht bei "shell = True")!
Eduardo Lucio

11

Ich weiß nicht, warum es nicht erwähnt wird, aber seit Python 3.5 gibt es einen neuen subprocess.rununiversellen Befehl (der ersetzen soll check_call, check_output...), der auch den timeoutParameter enthält.

subprocess.run (args, *, stdin = None, input = None, stdout = None, stderr = None, shell = False, cwd = None, timeout = None, check = False, encoding = None, error = None)

Run the command described by args. Wait for command to complete, then return a CompletedProcess instance.

Es wird eine subprocess.TimeoutExpiredAusnahme ausgelöst, wenn das Zeitlimit abgelaufen ist.


6

Hier ist meine Lösung, ich habe Thread und Event verwendet:

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 Aktion:

In [2]: exec_command(['sleep', '10'], 5)
Out[2]: ('', '', -9)

In [3]: exec_command(['sleep', '10'], 11)
Out[3]: ('', '', 0)

5

Die Lösung, die ich verwende, besteht darin, dem Shell-Befehl ein Zeitlimit voranzustellen . Wenn der Befehl zu lange dauert, stoppt ihn timelimit und Popen hat einen durch timelimit festgelegten Rückkehrcode. Wenn es> 128 ist, bedeutet dies, dass das Zeitlimit den Prozess beendet hat.

Siehe auch Python-Unterprozess mit Timeout und großer Ausgabe (> 64 KB)


Ich verwende ein ähnliches Tool namens timeout- packages.ubuntu.com/search?keywords=timeout - aber keines funktioniert unter Windows, oder?
Sridhar Ratnakumar

5

Ich habe die Lösung mit Threading von jcolladozu meinem Python-Modul easyprocess hinzugefügt .

Installieren:

pip install easyprocess

Beispiel:

from easyprocess import Proc

# shell is not supported!
stdout=Proc('ping localhost').call(timeout=1.5).stdout
print stdout

Das easyprocess-Modul ( code.activestate.com/pypm/easyprocess ) hat bei mir funktioniert, auch wenn es aus der Mehrfachverarbeitung stammt.
iChux

5

Wenn Sie Python 2 verwenden, probieren Sie es aus

import subprocess32

try:
    output = subprocess32.check_output(command, shell=True, timeout=3)
except subprocess32.TimeoutExpired as e:
    print e

1
Wahrscheinlich nicht unter Windows, wie in der ersten Frage gestellt
Jean-Francois T.

5

Das Voranstellen des Linux-Befehls timeoutist keine schlechte Problemumgehung und hat bei mir funktioniert.

cmd = "timeout 20 "+ cmd
subprocess.Popen(cmd.split(), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(output, err) = p.communicate()

Wie kann ich die ausgegebenen Zeichenfolgen während der Ausführung des Unterprozesses ausdrucken lassen? - Ausgangsnachrichten werden vom Unterprozess zurückgegeben.
Ammad

3

Ich habe implementiert, was ich aus einigen davon gewinnen konnte. Dies funktioniert unter Windows, und da dies ein Community-Wiki ist, würde ich wahrscheinlich auch meinen Code freigeben:

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

Dann aus einer anderen Klasse oder Datei:

        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()

Eigentlich funktioniert das wahrscheinlich nicht. Die terminate()Funktion markiert einen Thread als beendet, beendet den Thread jedoch nicht! Ich kann dies in * nix überprüfen, habe aber keinen Windows-Computer zum Testen.
Dotancohen

2

Sobald Sie die vollständigen Prozesslaufmaschinen in * unix verstanden haben, werden Sie leicht eine einfachere Lösung finden:

Betrachten Sie dieses einfache Beispiel, wie Sie mithilfe von select.select () ein Timeout für die Kommunikation () meth erstellen können (heutzutage fast überall auf * nix verfügbar). Dies kann auch mit epoll / poll / kqueue geschrieben werden, aber die Variante select.select () könnte ein gutes Beispiel für Sie sein. Die Hauptbeschränkungen von select.select () (Geschwindigkeit und maximal 1024 fds) sind für Ihre Aufgabe nicht anwendbar.

Dies funktioniert unter * nix, erstellt keine Threads, verwendet keine Signale, kann von jedem Thread (nicht nur von main) gestartet werden und reicht schnell aus, um 250 MB / s Daten von stdout auf meinem Computer zu lesen (i5 2,3 GHz).

Es gibt ein Problem beim Beitritt zu stdout / stderr am Ende der Kommunikation. Wenn Sie eine große Programmausgabe haben, kann dies zu einer großen Speichernutzung führen. Sie können communic () jedoch mehrmals mit kleineren Zeitüberschreitungen aufrufen.

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)

2
Dies behebt nur die Unix-Hälfte des Problems.
Spaceghost

2

Sie können dies mit tun 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


1

Obwohl ich es mir nicht ausführlich angesehen habe, scheint dieser Dekorateur, den ich bei ActiveState gefunden habe, für solche Dinge sehr nützlich zu sein. Zusammen mit subprocess.Popen(..., close_fds=True)zumindest bin ich bereit für die Shell-Scripting in Python.


Dieser Dekorateur verwendet signal.alarm, das unter Windows nicht verfügbar ist.
dbn

1

Diese Lösung beendet den Prozessbaum im Fall von shell = True, übergibt Parameter an den Prozess (oder nicht), hat eine Zeitüberschreitung und ruft die Ausgabe stdout, stderr und process des Rückrufs ab (sie verwendet psutil für kill_proc_tree). Dies basierte auf mehreren in SO veröffentlichten Lösungen, einschließlich jcollados. Posting als Antwort auf Kommentare von Anson und Jradice in der Antwort von jcollado. Getestet in Windows Srvr 2012 und Ubuntu 14.04. Bitte beachten Sie, dass Sie für Ubuntu den Aufruf parent.children (...) in parent.get_children (...) ändern müssen.

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()

1

Es gibt eine Idee, die Popen-Klasse zu unterordnen und sie mit einigen einfachen Methodendekoratoren zu erweitern. Nennen wir es ExpiringPopen.

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()

1

Ich hatte das Problem, dass ich einen Multithreading-Unterprozess beenden wollte, wenn er länger als eine bestimmte Zeitüberschreitungsdauer dauerte. Ich wollte eine Zeitüberschreitung festlegen Popen(), aber es hat nicht funktioniert. Dann wurde mir klar, dass dies Popen().wait()gleich ist, call()und so kam mir die Idee, eine Zeitüberschreitung innerhalb der .wait(timeout=xxx)Methode festzulegen , die schließlich funktionierte. Also habe ich es so gelöst:

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()

0

Leider bin ich an sehr strenge Richtlinien zur Offenlegung des Quellcodes durch meinen Arbeitgeber gebunden, sodass ich keinen tatsächlichen Code bereitstellen kann. Für meinen Geschmack besteht die beste Lösung darin, eine Unterklasse zu erstellen, die überschreibt, Popen.wait()um abzufragen, anstatt auf unbestimmte Zeit zu warten, und Popen.__init__einen Timeout-Parameter zu akzeptieren. Sobald Sie dies tun, funktionieren alle anderen PopenMethoden (die aufrufen wait) wie erwartet, einschließlich communicate.


0

https://pypi.python.org/pypi/python-subprocess2 bietet Erweiterungen für das Unterprozessmodul, mit denen Sie bis zu einem bestimmten Zeitraum warten können, andernfalls beenden.

Warten Sie also bis zu 10 Sekunden, bis der Vorgang beendet ist. Andernfalls beenden Sie Folgendes:

pipe  = subprocess.Popen('...')

timeout =  10

results = pipe.waitOrTerminate(timeout)

Dies ist sowohl mit Windows als auch mit Unix kompatibel. "results" ist ein Wörterbuch, das "returnCode" enthält, dh die Rückgabe der App (oder None, wenn sie beendet werden musste) sowie "actionTaken". Dies ist "SUBPROCESS2_PROCESS_COMPLETED", wenn der Prozess normal abgeschlossen wurde, oder eine Maske aus "SUBPROCESS2_PROCESS_TERMINATED" und SUBPROCESS2_PROCESS_KILLED, abhängig von den ergriffenen Maßnahmen (Einzelheiten finden Sie in der Dokumentation).


0

Verwenden Sie für 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

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

-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)

Dies ist ein Greuel
Corey Goldberg

-2

Ich habe nur versucht, etwas Einfacheres zu schreiben.

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

time.sleep (1) ist eine sehr schlechte Idee - stellen Sie sich vor, Sie möchten viele Befehle ausführen, die ungefähr 0,002 Sekunden dauern würden. Sie sollten lieber warten, während poll () (siehe select, für Linux-Epol empfohlen :)
ddzialak
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.