Comment démarrer un processus d'arrière-plan en Python?

295

J'essaie de porter un script shell vers la version python beaucoup plus lisible. Le script shell d'origine démarre plusieurs processus (utilitaires, moniteurs, etc.) en arrière-plan avec "&". Comment puis-je obtenir le même effet en python? J'aimerais que ces processus ne meurent pas lorsque les scripts python sont terminés. Je suis sûr que cela est lié au concept de démon d'une manière ou d'une autre, mais je n'ai pas trouvé comment le faire facilement.

Artem
la source
1
La question vraiment dupliquée est Comment lancer et exécuter un script externe en arrière-plan? .
Santé
1
Salut Artem. Veuillez accepter la réponse de Dan car (1) plus de votes, (2) subprocess.Popen()est la nouvelle méthode recommandée depuis 2010 (nous sommes en 2015 maintenant) et (3) la redondance des questions dupliquées ici a également une réponse acceptée subprocess.Popen().
Santé
1
@olibre En fait, la réponse devrait être subprocess.Popen("<command>")avec un fichier <command> dirigé par un shebang approprié. Fonctionne parfaitement pour moi (Debian) avec des scripts bash et python, implicitement shellet survit à son processus parent. stdoutva au même terminal que celui du parent. Donc, cela fonctionne un peu comme &dans un shell qui était une demande OP. Mais bon sang, toutes les questions
s'avèrent
Pour le fond, voir aussi stackoverflow.com/a/51950538/874188
tripleee

Réponses:

84

Remarque : Cette réponse est moins actuelle qu'elle ne l'était lors de sa publication en 2009. L'utilisation du subprocessmodule montré dans d'autres réponses est maintenant recommandée dans la documentation

(Notez que le module de sous-processus fournit des fonctionnalités plus puissantes pour générer de nouveaux processus et récupérer leurs résultats; l'utilisation de ce module est préférable à l'utilisation de ces fonctions.)


Si vous voulez que votre processus démarre en arrière-plan, vous pouvez soit l'utiliser system()et l'appeler de la même manière que votre script shell, soit le faire spawn:

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

(ou, alternativement, vous pouvez essayer le os.P_NOWAITdrapeau moins portable ).

Voir la documentation ici .

jkp
la source
8
Remarque: vous devez spécifier le chemin complet vers l'exécutable. Cette fonction n'utilisera pas la variable PATH et la variante qui l'utilise n'est pas disponible sous Windows.
Sorin
36
tout droit plante python pour moi.
pablo
29
os.P_DETACH a été remplacé par os.P_NOWAIT.
soi
7
Les personnes suggérant d'utiliser pourraient-elles subprocessnous donner un indice sur la façon de détacher un processus subprocess?
rakslice
3
Comment puis-je utiliser le script Python (par exemple attach.py) pour trouver un processus d'arrière-plan et rediriger son E / S afin que attach.py ​​puisse lire depuis / écrire vers some_long_running_prog en arrière-plan?
raof01
376

Alors que la solution de jkp fonctionne, la nouvelle façon de faire les choses (et la manière recommandée par la documentation) est d'utiliser le subprocessmodule. Pour les commandes simples, son équivalent, mais il offre plus d'options si vous voulez faire quelque chose de compliqué.

Exemple pour votre cas:

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

Cela s'exécutera rm -r some.fileen arrière-plan. Notez que l'appel .communicate()à l'objet renvoyé depuis Popensera bloqué jusqu'à ce qu'il se termine, alors ne le faites pas si vous voulez qu'il s'exécute en arrière-plan:

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

Voir la documentation ici .

Aussi, un point de clarification: "Contexte" tel que vous l'utilisez ici est purement un concept shell; techniquement, ce que vous voulez dire, c'est que vous voulez lancer un processus sans le bloquer pendant que vous attendez qu'il se termine. Cependant, j'ai utilisé «arrière-plan» ici pour faire référence à un comportement semblable à un arrière-plan du shell.

Dan
la source
7
@Dan: Comment puis-je tuer le processus une fois qu'il s'exécute en arrière-plan? Je veux l'exécuter pendant un certain temps (c'est un démon avec lequel j'interagis) et le tuer lorsque j'en ai fini. Les documents ne sont pas utiles ...
Juan
1
@ Dan, mais n'ai-je pas besoin de connaître le PID pour cela? Moniteur d'activité / gestionnaire de tâches n'est pas une option (doit se produire par programme).
Juan
5
ok alors comment forcez-vous le processus en arrière-plan lorsque vous avez besoin du résultat de Popen () pour écrire dans son stdin?
Michael
2
@JFSebastian: Je l'ai interprété comme "comment puis-je créer un processus indépendant qui n'arrête pas l'exécution du programme actuel". Comment suggéreriez-vous que je le modifie pour le rendre plus explicite?
Dan
1
@Dan: la bonne réponse est: utilisez Popen()pour éviter de bloquer le thread principal et si vous avez besoin d'un démon, regardez le python-daemonpaquet pour comprendre comment un démon bien défini doit se comporter. Votre réponse est correcte si vous supprimez tout en commençant par "Mais méfiez-vous" à l'exception du lien vers les documents des sous-processus.
jfs
47

Vous voulez probablement la réponse à "Comment appeler une commande externe en Python" .

L'approche la plus simple consiste à utiliser la os.systemfonction, par exemple:

import os
os.system("some_command &")

Fondamentalement, tout ce que vous transmettez à la systemfonction sera exécuté de la même manière que si vous l'aviez transmis au shell dans un script.

Eli Courtwright
la source
10
À mon humble avis, les scripts python sont généralement écrits pour être multiplateformes et s'il existe une solution multiplateforme simple, il est préférable de s'y tenir. Je ne sais jamais si vous devrez travailler avec une autre plate-forme à l'avenir :) Ou si un autre homme voudrait migrer votre script vers sa plate-forme.
d9k
7
Cette commande est synchrone (c'est-à-dire qu'elle attend toujours la fin du processus démarré).
TAV
@ d9k est-ce que os.system n'est pas portable?
lucid_dreamer
1
@ d9k n'est-il pas le choix d'exécuter quelque chose en arrière-plan qui vous positionne déjà en posix-land? Que feriez-vous sous Windows? Exécuter en tant que service?
lucid_dreamer
comment puis-je l'utiliser si j'ai besoin d'exécuter une commande à partir d'un dossier spécifique?
mrRobot
29

J'ai trouvé ça ici :

Sous Windows (win xp), le processus parent ne se terminera pas tant qu'il n'aura pas longtask.pyterminé son travail. Ce n'est pas ce que vous voulez dans le script CGI. Le problème n'est pas spécifique à Python, dans la communauté PHP les problèmes sont les mêmes.

La solution consiste à passer l' DETACHED_PROCESS indicateur de création de processus à la CreateProcessfonction sous-jacente dans l'API win. Si vous avez installé pywin32, vous pouvez importer le drapeau du module win32process, sinon vous devez le définir vous-même:

DETACHED_PROCESS = 0x00000008

pid = subprocess.Popen([sys.executable, "longtask.py"],
                       creationflags=DETACHED_PROCESS).pid
fp
la source
6
+1 pour montrer comment conserver l'ID de processus. Et si quelqu'un veut tuer le programme plus tard avec l'ID de processus: stackoverflow.com/questions/17856928/…
iChux
1
Cela ne semble que Windows
Audrius Meskauskas
24

À utiliser subprocess.Popen()avec le close_fds=Trueparamètre, qui permettra au sous-processus généré d'être détaché du processus Python lui-même et de continuer à s'exécuter même après la fermeture de Python.

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
la source
1
Dans Windows, il ne se détache pas, mais l'utilisation du paramètre creationflags fonctionne
SmartManoj
3
Cette solution laisse un sous-processus comme Zombie sur Linux.
TitanFighter
@TitanFighter cela peut être éviter par SIGCHLD ensemble SIG_IGN: stackoverflow.com/questions/16807603/...
sailfish009
merci @Jimmy votre réponse est la SEULE solution qui fonctionne pour moi.
sailfish009
12

Vous voudrez probablement commencer à étudier le module os pour forder différents threads (en ouvrant une session interactive et en émettant de l'aide (os)). Les fonctions pertinentes sont fork et n'importe laquelle des fonctions exec. Pour vous donner une idée sur la façon de commencer, mettez quelque chose comme ça dans une fonction qui exécute le fork (la fonction doit prendre une liste ou un tuple 'args' comme argument qui contient le nom du programme et ses paramètres; vous pouvez également vouloir pour définir stdin, out et err pour le nouveau thread):

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
la source
2
os.fork()est vraiment utile, mais il a un inconvénient notable d'être uniquement disponible sur * nix.
Evan Fosmark
Le seul problème avec os.fork est qu'il est spécifique à Win32.
jkp
Plus de détails sur cette approche: Création d'un démon à la manière de Python
Amir Ali Akbari
Vous pouvez également atteindre des effets similaires avec threading: stackoverflow.com/a/53751896/895245 Je pense que cela pourrait fonctionner sous Windows.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
9

Les deux capturent la sortie et fonctionnent en arrière-plan avec threading

Comme mentionné dans cette réponse , si vous capturez la sortie avec stdout=puis essayez de le faire read(), les blocs de processus.

Cependant, il y a des cas où vous en avez besoin. Par exemple, je voulais lancer deux processus qui parlent sur un port entre eux et enregistrer leur stdout dans un fichier journal et stdout.

Le threadingmodule nous permet de le faire.

Tout d'abord, regardez comment faire la partie de redirection de sortie seule dans cette question: Python Popen: Ecrivez simultanément sur stdout ET dans le fichier journal

Ensuite:

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)

Après l'exécution:

./main.py

stdout est mis à jour toutes les 0,5 secondes pour chaque ligne contenant:

0
10
1
11
2
12
3
13

et chaque fichier journal contient le journal correspondant pour un processus donné.

Inspiré par: https://eli.thegreenplace.net/2017/interacting-with-a-long-running-child-process-in-python/

Testé sur Ubuntu 18.04, Python 3.6.7.

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