Jak rozpocząć proces w tle w Pythonie?

295

Próbuję przenieść skrypt powłoki do znacznie bardziej czytelnej wersji Pythona. Oryginalny skrypt powłoki uruchamia w tle kilka procesów (programy narzędziowe, monitory itp.) Za pomocą „&”. Jak mogę osiągnąć ten sam efekt w Pythonie? Chciałbym, aby te procesy nie umarły po zakończeniu skryptów Pythona. Jestem pewien, że ma to jakoś związek z koncepcją demona, ale nie mogłem znaleźć łatwego sposobu.

Artem
źródło
1
Naprawdę zduplikowane pytanie brzmi: Jak uruchomić i uruchomić zewnętrzny skrypt w tle? . Pozdrawiam;)
olibre
1
Cześć Artem. Proszę przyjąć odpowiedź Dana, ponieważ (1) więcej głosów, (2) subprocess.Popen()to nowy zalecany sposób od 2010 r. (Jesteśmy teraz w 2015 r.) I (3) zduplikowane pytanie przekierowujące tutaj również ma akceptowaną odpowiedź na temat subprocess.Popen(). Pozdrawiam :-)
olibre
1
@olibre W rzeczywistości odpowiedź powinna brzmieć subprocess.Popen("<command>")w pliku <command> prowadzonym przez odpowiedni shebang. Działa idealnie dla mnie (Debian) ze skryptami bash i python, domyślnie shellsi przetrwa proces nadrzędny. stdoutidzie do tego samego terminalu, co rodzic. To działa podobnie jak &w powłoce, która była żądaniem OP. Ale do diabła, wszystkie pytania działają bardzo skomplikowane, a krótkie testy pokazały to w
mgnieniu oka
W tle może zobacz także stackoverflow.com/a/51950538/874188
tripleee

Odpowiedzi:

84

Uwaga : ta odpowiedź jest mniej aktualna niż w momencie opublikowania w 2009 roku. Korzystanie z subprocessmodułu przedstawionego w innych odpowiedziach jest teraz zalecane w dokumentacji

(Należy pamiętać, że moduł podprocesu zapewnia bardziej zaawansowane funkcje do tworzenia nowych procesów i wyszukiwania ich wyników; korzystanie z tego modułu jest lepsze niż korzystanie z tych funkcji).


Jeśli chcesz, aby proces rozpoczął się w tle, możesz użyć go system()i wywołać w taki sam sposób, jak skrypt powłoki lub możesz spawn:

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

(lub alternatywnie możesz wypróbować mniej przenośną os.P_NOWAITflagę).

Zobacz dokumentację tutaj .

jkp
źródło
8
Uwaga: musisz podać pełną ścieżkę do pliku wykonywalnego. Ta funkcja nie będzie używać zmiennej PATH, a wariant, który z niej korzysta, nie jest dostępny w systemie Windows.
sorin
36
prosto w górę powoduje awarię Pythona.
pablo
29
os.P_DETACH został zastąpiony przez os.P_NOWAIT.
się
7
Czy osoby sugerujące użycie mogą subprocessdać nam wskazówkę, jak odłączyć proces subprocess?
rakslice
3
Jak mogę użyć skryptu Python (powiedzmy attach.py), aby znaleźć proces w tle i przekierować swoje IO, aby attach.py ​​mógł czytać / zapisywać w tle jakiś długotrwały_prog?
raof01
376

Podczas gdy rozwiązanie jkp działa, nowszym sposobem robienia rzeczy (i zaleceniami dokumentacji) jest użycie subprocessmodułu. W przypadku prostych poleceń jest to odpowiednik, ale oferuje więcej opcji, jeśli chcesz zrobić coś skomplikowanego.

Przykład twojej sprawy:

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

To będzie działać rm -r some.filew tle. Zauważ, że wywołanie .communicate()zwróconego obiektu Popenbędzie blokować, dopóki się nie zakończy, więc nie rób tego, jeśli chcesz, aby działał w tle:

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

Zobacz dokumentację tutaj .

Należy również wyjaśnić: „Tło”, kiedy go używasz, jest wyłącznie koncepcją powłoki; technicznie masz na myśli to, że chcesz odrodzić proces bez blokowania go podczas oczekiwania na jego zakończenie. Jednak użyłem tutaj „tła”, aby odnieść się do zachowania przypominającego tło powłoki.

Dan
źródło
7
@Dan: Jak zabić proces, gdy działa w tle? Chcę go uruchomić przez chwilę (jest to demon, z którym mam interakcję) i zabić go, gdy skończę. Dokumenty nie są pomocne ...
Juan
1
@Dan, ale czy nie muszę za to znać PID? Monitor aktywności / Menedżer zadań nie jest opcją (musi nastąpić programowo).
Juan
5
ok, więc jak zmusić proces do tła, gdy potrzebujesz wyniku Popen () do zapisu na jego standardowe wejście?
Michael,
2
@JFSebastian: Zinterpretowałem to jako „jak mogę stworzyć niezależny proces, który nie zatrzyma wykonywania bieżącego programu”. Jak zasugerowałbyś, żebym go edytował, aby to bardziej wyrazić?
Dan
1
@ Dan: poprawna odpowiedź to: użyj, Popen()aby uniknąć blokowania głównego wątku, a jeśli potrzebujesz demona, spójrz na python-daemonpakiet, aby zrozumieć, jak powinien zachowywać się dobrze zdefiniowany demon. Twoja odpowiedź jest poprawna, jeśli usuniesz wszystko zaczynające się od „Ale bądź ostrożny”, z wyjątkiem linku do dokumentów podprocesu.
jfs
47

Prawdopodobnie potrzebujesz odpowiedzi na „Jak wywołać zewnętrzne polecenie w Pythonie” .

Najprostszym podejściem jest użycie os.systemfunkcji, np .:

import os
os.system("some_command &")

Zasadniczo wszystko, co przekażesz do systemfunkcji, zostanie wykonane tak samo, jakbyś przekazał ją do powłoki w skrypcie.

Eli Courtwright
źródło
9
IMHO, skrypty Pythona są zwykle pisane jako wieloplatformowe, a jeśli istnieje proste rozwiązanie wieloplatformowe, lepiej się z tym trzymać. Nigdy nie wiem, czy w przyszłości będziesz musiał współpracować z inną platformą :) Albo czy jakiś inny człowiek chciałby migrować twój skrypt na swoją platformę.
d9k
7
To polecenie jest synchroniczne (tzn. Zawsze czeka na zakończenie uruchomionego procesu).
tav
@ d9k czy system OS.system nie jest przenośny?
lucid_dreamer
1
@ d9k nie jest wybór, aby uruchomić coś w tle, co już pozycjonuje cię w posix-land? Co byś zrobił na Windowsie? Uruchomić jako usługę?
lucid_dreamer
jak mogę tego użyć, jeśli muszę uruchomić polecenie z określonego folderu?
mrRobot
29

Znalazłem to tutaj :

W systemie Windows (Win XP) proces nadrzędny nie zakończy się, dopóki longtask.pynie zakończy pracy. To nie jest to, czego chcesz w skrypcie CGI. Problem nie jest specyficzny dla Pythona, w społeczności PHP problemy są takie same.

Rozwiązaniem jest przekazanie DETACHED_PROCESS flagi tworzenia procesu do podstawowej CreateProcessfunkcji w win API. Jeśli akurat masz zainstalowany pywin32, możesz zaimportować flagę z modułu win32process, w przeciwnym razie powinieneś ją zdefiniować samodzielnie:

DETACHED_PROCESS = 0x00000008

pid = subprocess.Popen([sys.executable, "longtask.py"],
                       creationflags=DETACHED_PROCESS).pid
fp
źródło
6
+1 za pokazanie, jak zachować identyfikator procesu. A jeśli ktoś chce później zabić program z identyfikatorem procesu: stackoverflow.com/questions/17856928/...
iChux
1
Wydaje się, że to tylko Windows
Audrius Meskauskas
24

Używaj subprocess.Popen()z close_fds=Trueparametrem, który pozwoli odłączyć spawnowany podproces od samego procesu Pythona i kontynuować działanie nawet po wyjściu z Pythona.

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
źródło
1
W systemie Windows nie odłącza się, ale działa przy użyciu parametru
createflags
3
To rozwiązanie pozostawia podproces jako Zombie w systemie Linux.
TitanFighter,
@TitanFighter można tego uniknąć, ustawiając SIGCHLD SIG_IGN: stackoverflow.com/questions/16807603/…
sailfish009
dzięki @ Jimmy twoja odpowiedź jest JEDYNYM rozwiązaniem, które działa dla mnie.
sailfish009
12

Prawdopodobnie chcesz rozpocząć badanie modułu os w celu tworzenia różnych wątków (otwierając sesję interaktywną i udzielając pomocy (os)). Odpowiednimi funkcjami są fork i dowolne z nich. Aby dać ci pomysł, jak zacząć, umieść coś takiego w funkcji wykonującej rozwidlenie (funkcja musi wziąć listę lub krotkę „argumenty” jako argument zawierający nazwę programu i jego parametry; możesz również chcieć aby zdefiniować standardowe wejście, wyjście i błąd dla nowego wątku):

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
źródło
2
os.fork()jest naprawdę przydatny, ale ma znaczącą wadę, ponieważ jest dostępny tylko na * nix.
Evan Fosmark
Jedyny problem z os.fork polega na tym, że jest on specyficzny dla Win32.
jkp
Więcej szczegółów na temat tego podejścia: Tworzenie demona w języku Python
Amir Ali Akbari
Możesz także osiągnąć podobne efekty za pomocą threading: stackoverflow.com/a/53751896/895245 Myślę, że to może działać w systemie Windows.
Ciro Santilli 12 冠状 病 六四 事件 法轮功
9

Oba wychwytują dane wyjściowe i działają w tle za pomocą threading

Jak wspomniano w tej odpowiedzi , jeśli przechwycisz wynik za pomocą, stdout=a następnie spróbujesz read(), proces zostanie zablokowany.

Są jednak przypadki, w których jest to potrzebne. Na przykład chciałem uruchomić dwa procesy, które rozmawiają przez port między nimi , i zapisać ich stdout w pliku dziennika i stdout.

threadingModuł pozwala nam to zrobić.

Najpierw spójrz na to, jak wykonać część przekierowania wyjścia samodzielnie w tym pytaniu: Python Popen: Zapisuj jednocześnie do standardowego pliku dziennika AND

Następnie:

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)

Po bieganiu:

./main.py

stdout jest aktualizowany co 0,5 sekundy dla każdych dwóch wierszy, które zawierają:

0
10
1
11
2
12
3
13

a każdy plik dziennika zawiera odpowiedni dziennik dla danego procesu.

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

Testowane na Ubuntu 18.04, Python 3.6.7.

Ciro Santilli
źródło