Como iniciar um processo em segundo plano no Python?

295

Estou tentando portar um script de shell para a versão python muito mais legível. O script shell original inicia vários processos (utilitários, monitores, etc.) em segundo plano com "&". Como posso obter o mesmo efeito em python? Eu gostaria que esses processos não morressem quando os scripts python forem concluídos. Tenho certeza de que está relacionado ao conceito de um daemon de alguma forma, mas não consegui descobrir como fazer isso facilmente.

Artem
fonte
1
A pergunta realmente duplicada é como iniciar e executar scripts externos em segundo plano? . Cheers;)
olibre 13/11
1
Oi Artem. Aceite a resposta de Dan porque (1) mais votos, (2) subprocess.Popen()é a nova maneira recomendada desde 2010 (estamos em 2015 agora) e (3) a pergunta duplicada redirecionada aqui também tem uma resposta aceita subprocess.Popen(). Cheers :-)
olibre 26/06
1
@olibre Na verdade, a resposta deve estar subprocess.Popen("<command>")com o arquivo < comand > liderado por um shebang adequado. Funciona perfeito para mim (Debian) com scripts bash e python, implicitamente se shellsobrevive ao processo pai. stdoutvai para o mesmo terminal que o dos pais. Portanto, isso funciona como &em um shell que foi solicitado pelo OP. Mas inferno, todas as perguntas funcionar muito complexo, enquanto um pouco de testes mostraram que em nenhum momento;)
flaschbier
Para obter mais informações, consulte também stackoverflow.com/a/51950538/874188
tripleee

Respostas:

84

Nota : esta resposta é menos atual do que era quando postada em 2009. O uso do subprocessmódulo mostrado em outras respostas agora é recomendado nos documentos

(Observe que o módulo de subprocesso fornece recursos mais poderosos para gerar novos processos e recuperar seus resultados; usar esse módulo é preferível a usar essas funções.)


Se você deseja que seu processo inicie em segundo plano, você pode usá system()-lo e chamá-lo da mesma maneira que seu script shell, ou você pode spawn:

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

(ou, alternativamente, você pode tentar o os.P_NOWAITsinalizador menos portátil ).

Veja a documentação aqui .

jkp
fonte
8
Observação: você deve especificar o caminho completo para o executável. Esta função não usará a variável PATH e a variante que a usa não está disponível no Windows.
Sorin
36
direto trava python para mim.
28611 pablo
29
os.P_DETACH foi substituído por os.P_NOWAIT.
se
7
As pessoas que sugerem o uso podem subprocessnos dar uma dica de como desanexar um processo subprocess?
rakslice
3
Como posso usar o script Python (por exemplo, attach.py) para encontrar um processo em segundo plano e redirecionar sua IO para que o attach.py ​​possa ler / gravar em some_long_running_prog em segundo plano?
raof01 5/03
376

Enquanto a solução da jkp funciona, a maneira mais nova de fazer as coisas (e a maneira como a documentação recomenda) é usar o subprocessmódulo. Para comandos simples é equivalente, mas oferece mais opções se você quiser fazer algo complicado.

Exemplo para o seu caso:

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

Isso será executado rm -r some.fileem segundo plano. Observe que a chamada .communicate()do objeto retornado Popenserá bloqueada até que seja concluída, portanto, não faça isso se quiser que ele seja executado em segundo plano:

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

Consulte a documentação aqui .

Além disso, um ponto de esclarecimento: "Antecedentes", como você os utiliza aqui, é puramente um conceito de shell; tecnicamente, o que você quer dizer é que deseja gerar um processo sem bloquear enquanto espera que ele seja concluído. No entanto, usei "background" aqui para se referir ao comportamento semelhante ao do shell.

Dan
fonte
7
@ Dan: Como eu mato o processo depois que ele é executado em segundo plano? Quero executá-lo por um tempo (é um daemon com o qual interajo) e matá-lo quando terminar. Os documentos não são úteis ...
Juan
1
@ Dan, mas não preciso saber o PID para isso? O monitor de atividades / gerenciador de tarefas não é uma opção (precisa acontecer programaticamente).
Juan
5
ok, então, como você força o processo para segundo plano quando precisa que o resultado de Popen () escreva para seu stdin?
Michael
2
@JFSebastian: interpretei como "como posso criar um processo independente que não interrompa a execução do programa atual". Como você sugere que eu a edite para tornar isso mais explícito?
21414 Dan
1
@ Dan: a resposta correta é: use Popen()para evitar o bloqueio do thread principal e, se você precisar de um daemon, consulte o python-daemonpacote para entender como um daemon bem definido deve se comportar. Sua resposta está correta se você remover tudo que começar com "Mas tenha cuidado", exceto pelo link para os documentos do subprocesso.
JFS
47

Você provavelmente deseja a resposta para "Como chamar um comando externo em Python" .

A abordagem mais simples é usar a os.systemfunção, por exemplo:

import os
os.system("some_command &")

Basicamente, tudo o que você passar para a systemfunção será executado da mesma forma como se você a tivesse passado para o shell em um script.

Eli Courtwright
fonte
9
IMHO, os scripts python geralmente são escritos para serem multiplataforma e, se existir uma solução simples entre plataformas, é melhor ficar com ela. Nunca se sabe se você terá que trabalhar com outra plataforma no futuro :) Ou se algum outro homem deseja migrar seu script para a plataforma dele.
d9k
7
Este comando é síncrono (ou seja, espera sempre pelo término do processo iniciado).
TAV
@ d9k o os.system não é portátil?
Lucid_dreamer
1
@ d9k: a opção de executar algo em segundo plano já o posiciona em posix-land? O que você faria no Windows? Executar como um serviço?
Lucid_dreamer
como posso usar isso se precisar executar um comando de uma pasta específica?
mrRobot
29

Encontrei isso aqui :

No windows (win xp), o processo pai não será concluído até que o trabalho longtask.pyseja concluído. Não é o que você deseja no script CGI. O problema não é específico do Python, na comunidade PHP os problemas são os mesmos.

A solução é passar o DETACHED_PROCESS Sinalizador de criação de processo para a CreateProcessfunção subjacente na API do win. Se você instalou o pywin32, pode importar o sinalizador do módulo win32process, caso contrário, você deve defini-lo:

DETACHED_PROCESS = 0x00000008

pid = subprocess.Popen([sys.executable, "longtask.py"],
                       creationflags=DETACHED_PROCESS).pid
fp
fonte
6
+1 para mostrar como manter o ID do processo. E se alguém quiser matar o programa mais tarde com o ID do processo: stackoverflow.com/questions/17856928/…
#
1
Isto parece somente para Windows
Audrius Meškauskas
24

Use subprocess.Popen()com o close_fds=Trueparâmetro, que permitirá que o subprocesso gerado seja desconectado do próprio processo Python e continue em execução mesmo após a saída do 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
fonte
1
Nas janelas, não retire mas usando creationflags parâmetro funciona
SmartManoj
3
Essa solução deixa um subprocesso como Zombie no Linux.
TitanFighter 3/04/19
@TitanFighter isso pode ser evitado definindo SIGCHLD SIG_IGN: stackoverflow.com/questions/16807603/…
sailfish009
obrigado @ Jimmy, sua resposta é que a única solução funciona para mim.
sailfish009
12

Você provavelmente deseja começar a investigar o módulo os para criar bifurcações diferentes (abrindo uma sessão interativa e emitindo ajuda (os)). As funções relevantes são fork e qualquer uma das exec. Para lhe dar uma idéia de como iniciar, coloque algo como isto em uma função que executa o fork (a função precisa usar uma lista ou tupla 'args' como um argumento que contenha o nome do programa e seus parâmetros; você também pode querer para definir stdin, out e err para o novo 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
fonte
2
os.fork()é realmente útil, mas tem uma desvantagem notável de estar disponível apenas no * nix.
Evan Fosmark
O único problema com o os.fork é que ele é específico do win32.
Jjp
Mais detalhes sobre esta abordagem: Criando um daemon a maneira Python
Amir Ali Akbari
Você também pode obter efeitos semelhantes com threading: stackoverflow.com/a/53751896/895245 Acho que isso pode funcionar no Windows.
Ciro Santilli escreveu:
9

Capture a saída e execute em segundo plano com threading

Conforme mencionado nesta resposta , se você capturar a saída com stdout=e depois tentar read(), o processo será bloqueado.

No entanto, há casos em que você precisa disso. Por exemplo, eu queria iniciar dois processos que conversam sobre uma porta entre eles e salvar o stdout em um arquivo de log e stdout.

O threadingmódulo nos permite fazer isso.

Primeiro, veja como fazer a parte do redirecionamento de saída sozinha nesta pergunta: Python Popen: Escreva no stdout AND no arquivo de log simultaneamente

Então:

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)

Depois de correr:

./main.py

O stdout é atualizado a cada 0,5 segundos para que cada duas linhas contenha:

0
10
1
11
2
12
3
13

e cada arquivo de log contém o respectivo log para um determinado processo.

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

Testado no Ubuntu 18.04, Python 3.6.7.

Ciro Santilli adicionou uma nova foto
fonte