Wie starte ich einen Hintergrundprozess in Python?

295

Ich versuche, ein Shell-Skript auf die viel besser lesbare Python-Version zu portieren. Das ursprüngliche Shell-Skript startet mehrere Prozesse (Dienstprogramme, Monitore usw.) im Hintergrund mit "&". Wie kann ich den gleichen Effekt in Python erzielen? Ich möchte, dass diese Prozesse nicht absterben, wenn die Python-Skripte abgeschlossen sind. Ich bin mir sicher, dass es irgendwie mit dem Konzept eines Daemons zusammenhängt, aber ich konnte nicht einfach herausfinden, wie das geht.

Artem
quelle
1
Die wirklich doppelte Frage lautet: Wie starte und starte ich ein externes Skript im Hintergrund? . Prost;)
Olibre
1
Hallo Artem. Bitte akzeptieren Sie Dans Antwort, da (1) mehr Stimmen, (2) subprocess.Popen()der neue empfohlene Weg seit 2010 ist (wir sind jetzt im Jahr 2015) und (3) die doppelte Frage, die hier umgeleitet wird, auch eine akzeptierte Antwort über hat subprocess.Popen(). Prost :-)
Olibre
1
@olibre Tatsächlich sollte die Antwort subprocess.Popen("<command>")mit einer <Befehl> -Datei erfolgen, die von einem geeigneten Shebang geführt wird. Funktioniert perfekt für mich (Debian) mit Bash- und Python-Skripten, impliziert implizit shells und überlebt den übergeordneten Prozess. stdoutgeht zum selben Terminal wie das der Eltern. Das funktioniert also ähnlich wie &in einer Shell, die von OPs angefordert wurde. Aber zum Teufel, alle Fragen
klappen
Für Hintergrund siehe vielleicht auch stackoverflow.com/a/51950538/874188
Tripleee

Antworten:

84

Hinweis : Diese Antwort ist weniger aktuell als 2009. Die Verwendung des subprocessin anderen Antworten gezeigten Moduls wird jetzt in den Dokumenten empfohlen

(Beachten Sie, dass das Unterprozessmodul leistungsfähigere Funktionen zum Laichen neuer Prozesse und zum Abrufen ihrer Ergebnisse bietet. Die Verwendung dieses Moduls ist der Verwendung dieser Funktionen vorzuziehen.)


Wenn Sie möchten, dass Ihr Prozess im Hintergrund startet, können Sie ihn entweder system()wie Ihr Shell-Skript verwenden und aufrufen oder wie folgt spawn:

import os
os.spawnl(os.P_DETACH, 'some_long_running_command')

(oder alternativ können Sie die weniger tragbare os.P_NOWAITFlagge ausprobieren ).

Siehe die Dokumentation hier .

jkp
quelle
8
Anmerkung: Sie müssen den vollständigen Pfad zur ausführbaren Datei angeben. Diese Funktion verwendet die Variable PATH nicht und die Variante, die sie verwendet, ist unter Windows nicht verfügbar.
Sorin
36
Straight Up stürzt Python für mich ab.
Pablo
29
os.P_DETACH wurde durch os.P_NOWAIT ersetzt.
selbst
7
Könnten subprocessuns die Leute, die die Verwendung vorschlagen , einen Hinweis geben, wie man einen Prozess löst subprocess?
Rakslice
3
Wie kann ich ein Python-Skript (z. B. attach.py) verwenden, um einen Hintergrundprozess zu finden und dessen E / A umzuleiten, damit attach.py ​​im Hintergrund von some_long_running_prog lesen / schreiben kann?
raof01
376

Während die Lösung von jkp funktioniert, besteht die neuere Vorgehensweise (und die in der Dokumentation empfohlene Vorgehensweise) darin, das subprocessModul zu verwenden. Für einfache Befehle ist es gleichbedeutend, aber es bietet mehr Optionen, wenn Sie etwas Kompliziertes tun möchten.

Beispiel für Ihren Fall:

import subprocess
subprocess.Popen(["rm","-r","some.file"])

Dies wird ausgeführt rm -r some.file im Hintergrund ausgeführt. Beachten Sie, dass der Aufruf .communicate()des von zurückgegebenen Objekts Popenblockiert wird, bis es abgeschlossen ist. Tun Sie dies also nicht, wenn Sie möchten, dass es im Hintergrund ausgeführt wird:

import subprocess
ls_output=subprocess.Popen(["sleep", "30"])
ls_output.communicate()  # Will block for 30 seconds

Siehe die Dokumentation hier .

Zur Verdeutlichung auch: "Hintergrund", wie Sie ihn hier verwenden, ist ein reines Shell-Konzept; Technisch gesehen meinen Sie damit, dass Sie einen Prozess erzeugen möchten, ohne ihn zu blockieren, während Sie darauf warten, dass er abgeschlossen ist. Ich habe hier jedoch "Hintergrund" verwendet, um auf Shell-Hintergrund-ähnliches Verhalten zu verweisen.

Dan
quelle
7
@ Dan: Wie kann ich den Prozess beenden, wenn er im Hintergrund ausgeführt wird? Ich möchte es für eine Weile ausführen (es ist ein Daemon, mit dem ich interagiere) und es beenden, wenn ich damit fertig bin. Die Dokumente sind nicht hilfreich ...
Juan
1
@ Dan, aber muss ich die PID dafür nicht kennen? Aktivitätsmonitor / Task-Manager ist keine Option (muss programmgesteuert erfolgen).
Juan
5
ok also wie erzwingst du den prozess in den hintergrund, wenn du das ergebnis von popen () zum schreiben in sein stdin brauchst?
Michael
2
@JFSebastian: Ich habe es als "Wie kann ich einen unabhängigen Prozess erstellen, der die Ausführung des aktuellen Programms nicht stoppt" interpretiert. Wie würden Sie vorschlagen, dass ich es bearbeite, um dies deutlicher zu machen?
Dan
1
@Dan: Die richtige Antwort lautet: Verwenden Sie diese Option Popen(), um das Blockieren des Hauptthreads zu vermeiden. Wenn Sie einen Daemon benötigen, schauen Sie sich das python-daemonPaket an, um zu verstehen, wie sich ein genau definierter Daemon verhalten soll. Ihre Antwort ist in Ordnung, wenn Sie alles entfernen, was mit "Aber seien Sie vorsichtig" beginnen, mit Ausnahme des Links zu den Dokumenten des Unterprozesses.
JFS
47

Sie möchten wahrscheinlich die Antwort auf "So rufen Sie einen externen Befehl in Python auf" .

Der einfachste Ansatz ist die Verwendung der os.systemFunktion, z.

import os
os.system("some_command &")

Grundsätzlich wird alles, was Sie an die systemFunktion übergeben, so ausgeführt, als hätten Sie es in einem Skript an die Shell übergeben.

Eli Courtwright
quelle
9
Meiner Meinung nach werden Python-Skripte normalerweise plattformübergreifend geschrieben. Wenn es eine einfache plattformübergreifende Lösung gibt, ist es besser, sich daran zu halten. Nie wissen, ob Sie in Zukunft mit einer anderen Plattform arbeiten müssen :) Oder ob ein anderer Mann Ihr Skript auf seine Plattform migrieren möchte.
d9k
7
Dieser Befehl ist synchron (dh er wartet immer auf die Beendigung des gestarteten Prozesses).
7.
@ d9k ist os.system nicht portabel?
lucid_dreamer
1
@ d9k ist es nicht die Wahl, etwas im Hintergrund auszuführen, das dich bereits im Posix-Land positioniert? Was würden Sie unter Windows tun? Als Dienst ausführen?
lucid_dreamer
Wie kann ich dies verwenden, wenn ich einen Befehl aus einem bestimmten Ordner ausführen muss?
MrRobot
29

Ich habe das hier gefunden :

Unter Windows (Win XP) wird der übergeordnete Prozess erst beendet, wenn der longtask.pyseine Arbeit beendet hat. Es ist nicht das, was Sie in CGI-Skript wollen. Das Problem ist nicht spezifisch für Python. In der PHP-Community sind die Probleme dieselben.

Die Lösung besteht darin, das DETACHED_PROCESS Prozesserstellungsflag an die zugrunde liegende CreateProcessFunktion in der Win-API zu übergeben. Wenn Sie pywin32 installiert haben, können Sie das Flag aus dem win32process-Modul importieren. Andernfalls sollten Sie es selbst definieren:

DETACHED_PROCESS = 0x00000008

pid = subprocess.Popen([sys.executable, "longtask.py"],
                       creationflags=DETACHED_PROCESS).pid
fp
quelle
6
+1, um zu zeigen, wie die Prozess-ID beibehalten wird. Und wenn jemand das Programm später mit der Prozess-ID beenden
möchte
1
Dies scheint nur Windows
Audrius Meskauskas
24

Verwenden Sie subprocess.Popen()mit demclose_fds=True Parameter, der den erzeugte Unterprozess ermöglicht es von dem Python abgenommen wird Prozess selbst und auch nach Python - Exits ausgeführt wird .

https://gist.github.com/yinjimmy/d6ad0742d03d54518e9f

import os, time, sys, subprocess

if len(sys.argv) == 2:
    time.sleep(5)
    print 'track end'
    if sys.platform == 'darwin':
        subprocess.Popen(['say', 'hello'])
else:
    print 'main begin'
    subprocess.Popen(['python', os.path.realpath(__file__), '0'], close_fds=True)
    print 'main end'
Jimmy Yin
quelle
1
In Windows wird es nicht getrennt, aber die Verwendung von Creationflags-Parametern funktioniert
SmartManoj
3
Diese Lösung hinterlässt unter Linux einen Unterprozess als Zombie.
TitanFighter
@TitanFighter Dies kann vermieden werden, indem SIGCHLD SIG_IGN gesetzt wird: stackoverflow.com/questions/16807603/…
Segelfisch009
danke @Jimmy deine Antwort ist die EINZIGE Lösung, die für mich funktioniert.
Segelfisch009
12

Sie möchten wahrscheinlich das OS-Modul untersuchen, um verschiedene Threads zu forken (indem Sie eine interaktive Sitzung öffnen und Hilfe (OS) ausgeben). Die relevanten Funktionen sind Fork und alle Exec-Funktionen. Um Ihnen eine Vorstellung davon zu geben, wie Sie beginnen sollen, fügen Sie so etwas in eine Funktion ein, die die Verzweigung ausführt (die Funktion muss eine Liste oder ein Tupel 'args' als Argument verwenden, das den Namen des Programms und seine Parameter enthält. Möglicherweise möchten Sie dies auch stdin, out und err für den neuen Thread definieren):

try:
    pid = os.fork()
except OSError, e:
    ## some debug output
    sys.exit(1)
if pid == 0:
    ## eventually use os.putenv(..) to set environment variables
    ## os.execv strips of args[0] for the arguments
    os.execv(args[0], args)
Gerald Senarclens de Grancy
quelle
2
os.fork()ist wirklich nützlich, hat aber den bemerkenswerten Nachteil, dass es nur auf * nix verfügbar ist.
Evan Fosmark
Das einzige Problem mit os.fork ist, dass es win32-spezifisch ist.
JKP
Weitere Details zu diesem Ansatz: Erstellen eines Daemons nach Python-Art
Amir Ali Akbari
Sie können ähnliche Effekte auch erzielen mit threading: stackoverflow.com/a/53751896/895245 Ich denke, das könnte unter Windows funktionieren.
Ciro Santilli 法轮功 冠状 病 六四. 法轮功
9

Beide erfassen die Ausgabe und laufen im Hintergrund mit threading

Wie in dieser Antwort erwähnt , blockiert der Prozess , wenn Sie die Ausgabe mit erfassen stdout=und dann versuchen read(), dies zu tun.

Es gibt jedoch Fälle, in denen Sie dies benötigen. Zum Beispiel wollte ich zwei Prozesse starten , die über einen Port zwischen ihnen kommunizieren, und ihre Standardausgabe in einer Protokolldatei und Standardausgabe speichern.

Das threadingModul ermöglicht es uns, dies zu tun.

Schauen Sie sich zunächst in dieser Frage an, wie der Ausgabeumleitungsteil alleine ausgeführt wird: ausführen: Python Popen: Schreiben Sie gleichzeitig in die Standard- UND Protokolldatei

Dann:

main.py.

#!/usr/bin/env python3

import os
import subprocess
import sys
import threading

def output_reader(proc, file):
    while True:
        byte = proc.stdout.read(1)
        if byte:
            sys.stdout.buffer.write(byte)
            sys.stdout.flush()
            file.buffer.write(byte)
        else:
            break

with subprocess.Popen(['./sleep.py', '0'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) as proc1, \
     subprocess.Popen(['./sleep.py', '10'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) as proc2, \
     open('log1.log', 'w') as file1, \
     open('log2.log', 'w') as file2:
    t1 = threading.Thread(target=output_reader, args=(proc1, file1))
    t2 = threading.Thread(target=output_reader, args=(proc2, file2))
    t1.start()
    t2.start()
    t1.join()
    t2.join()

sleep.py

#!/usr/bin/env python3

import sys
import time

for i in range(4):
    print(i + int(sys.argv[1]))
    sys.stdout.flush()
    time.sleep(0.5)

Nach dem Rennen:

./main.py

stdout wird alle 0,5 Sekunden aktualisiert, damit alle zwei Zeilen Folgendes enthalten:

0
10
1
11
2
12
3
13

und jede Protokolldatei enthält das entsprechende Protokoll für einen bestimmten Prozess.

Inspiriert von: https://eli.thegreenplace.net/2017/interacting-with-a-long-running-child-process-in-python/

Getestet unter Ubuntu 18.04, Python 3.6.7.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
quelle