Como finalizar um subprocesso python iniciado com shell = True

308

Estou iniciando um subprocesso com o seguinte comando:

p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)

No entanto, quando tento matar usando:

p.terminate()

ou

p.kill()

O comando continua sendo executado em segundo plano, então eu queria saber como posso realmente finalizar o processo.

Observe que quando executo o comando com:

p = subprocess.Popen(cmd.split(), stdout=subprocess.PIPE)

Ele termina com êxito ao emitir o arquivo p.terminate().

user175259
fonte
Como é sua cmdaparência? Pode conter um comando que aciona vários processos a serem iniciados. Portanto, não está claro de qual processo você fala.
Robert Siemer
1
não shell=Truefaz uma grande diferença?
Charlie Parker

Respostas:

410

Use um grupo de processos para permitir o envio de um sinal para todo o processo nos grupos. Para isso, você deve anexar uma identificação de sessão ao processo pai dos processos gerados / filhos, que é um shell no seu caso. Isso o tornará o líder do grupo nos processos. Portanto, agora, quando um sinal é enviado ao líder do grupo de processos, ele é transmitido a todos os processos filhos desse grupo.

Aqui está o código:

import os
import signal
import subprocess

# The os.setsid() is passed in the argument preexec_fn so
# it's run after the fork() and before  exec() to run the shell.
pro = subprocess.Popen(cmd, stdout=subprocess.PIPE, 
                       shell=True, preexec_fn=os.setsid) 

os.killpg(os.getpgid(pro.pid), signal.SIGTERM)  # Send the signal to all the process groups
mouad
fonte
2
@PiotrDobrogost: Infelizmente não, porque os.setsidnão está disponível no Windows ( docs.python.org/library/os.html#os.setsid ), não sei se isso pode ajudar, mas você pode procurar aqui ( bugs.python.org / issue5115 ) para obter algumas dicas sobre como fazê-lo.
Mouad 19/10/12
6
Como se subprocess.CREATE_NEW_PROCESS_GROUPrelaciona com isso?
Piotr Dobrogost 19/10/12
11
nossos testes sugerem que setsid! = setpgid e que os.pgkill mata apenas subprocessos que ainda possuem o mesmo ID de grupo de processos. processos que têm grupo de processos alterada não são mortos, embora eles ainda podem ter o mesmo ID de sessão ...
hwjp
4
Eu não recomendaria fazer os.setsid (), pois também tem outros efeitos. Entre outros, desconecta o TTY controlador e faz do novo processo um líder de grupo de processos. Veja win.tue.nl/~aeb/linux/lk/lk-10.html
parasietje
92
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)
p.kill()

p.kill()acaba matando o processo do shell e cmdainda está em execução.

Encontrei uma correção conveniente por:

p = subprocess.Popen("exec " + cmd, stdout=subprocess.PIPE, shell=True)

Isso fará com que o cmd herde o processo do shell, em vez de o shell iniciar um processo filho, que não é morto. p.pidserá o ID do seu processo de cmd então.

p.kill() Deveria trabalhar.

Não sei que efeito isso terá no seu cachimbo.

Bryant Hansen
fonte
1
Solução agradável e leve para * nix, obrigado! Funciona no Linux, também deve funcionar no Mac.
MarSoft
Solução muito boa. Se o seu cmd for um wrapper de script de shell para outra coisa, chame o binário final lá com exec também para ter apenas um subprocesso.
Nicolinux 04/08/19
1
Isso é lindo. Eu tenho tentado descobrir como gerar e matar um subprocesso por espaço de trabalho no Ubuntu. Essa resposta me ajudou. Gostaria de poder upvote-lo mais de uma vez
Sergiy Kolodyazhnyy
2
isso não funciona se um ponto e vírgula é usada no cmd
gnr
@speedyrazor - Não funciona no Windows10. Acho que as respostas específicas devem ser claramente marcadas como tal.
DarkLight 12/09/19
48

Se você pode usar o psutil , isso funciona perfeitamente:

import subprocess

import psutil


def kill(proc_pid):
    process = psutil.Process(proc_pid)
    for proc in process.children(recursive=True):
        proc.kill()
    process.kill()


proc = subprocess.Popen(["infinite_app", "param"], shell=True)
try:
    proc.wait(timeout=3)
except subprocess.TimeoutExpired:
    kill(proc.pid)
Jovik
fonte
3
AttributeError: 'Process' object has no attribute 'get_childrenpara pip install psutil.
d33tah
3
Eu acho que get_children () deve ser criança (). Mas não funcionou para mim no Windows, o processo ainda está lá.
Godsmith
3
@ Godsmith - a API do psutil mudou e você está certo: children () faz a mesma coisa que get_children () costumava fazer. Se não funcionar no Windows, convém criar um ticket de bug no GitHub
Jovik 22/15/15
24

Eu poderia fazer isso usando

from subprocess import Popen

process = Popen(command, shell=True)
Popen("TASKKILL /F /PID {pid} /T".format(pid=process.pid))

matou o cmd.exe programa que eu dei o comando.

(No Windows)

SPratap
fonte
Ou use o nome do processo : Popen("TASKKILL /F /IM " + process_name)se você não o tiver, poderá obtê-lo a partir do commandparâmetro
zvi 9/01
12

Quando shell=Trueo shell é o processo filho e os comandos são seus filhos. Portanto, qualquer um SIGTERMou SIGKILLmatará o shell, mas não seus processos filhos, e não me lembro de uma boa maneira de fazê-lo. A melhor maneira de pensar é usar shell=False, caso contrário, quando você mata o processo de shell pai, ele deixa um processo de shell desativado.

Sai Venkat
fonte
8

Nenhuma dessas respostas funcionou para mim, então estou deixando o código que funcionou. No meu caso, mesmo depois de encerrar o processo .kill()e obter uma.poll() encerrar código de retorno, o processo não foi encerrado.

Seguindo a subprocess.Popen documentação :

"... para limpar adequadamente um aplicativo bem-comportado, deve matar o processo filho e concluir a comunicação ..."

proc = subprocess.Popen(...)
try:
    outs, errs = proc.communicate(timeout=15)
except TimeoutExpired:
    proc.kill()
    outs, errs = proc.communicate()

No meu caso, estava faltando a proc.communicate()chamada depois proc.kill(). Isso limpa o processo stdin, stdout ... e encerra o processo.

epinal
fonte
Esta solução não funciona para mim no linux e Python 2.7
user9869932
@xyz Funcionou para mim no Linux e python 3.5. Verifique os documentos para python 2.7
epinal
@espinal, obrigado, sim. É possivelmente um problema de linux. É o linux Raspbian rodando em um Raspberry 3 #
9289932
5

Como Sai disse, o shell é o filho, então os sinais são interceptados por ele - a melhor maneira que eu encontrei é usar shell = False e usar shlex para dividir a linha de comando:

if isinstance(command, unicode):
    cmd = command.encode('utf8')
args = shlex.split(cmd)

p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)

Então p.kill () e p.terminate () devem funcionar como você espera.

Matt Billenstein
fonte
1
No meu caso, isso realmente não ajuda, já que o cmd é "cd path && zsync etc etc". Então isso realmente faz com que o comando falhe!
precisa saber é o seguinte
4
Use caminhos absolutos em vez de mudar de diretórios ... Opcionalmente os.chdir (...) para que diretório ...
Matt Billenstein
A capacidade de alterar o diretório de trabalho do processo filho está integrada. Apenas passe o cwdargumento para Popen.
Jonathon Reinhart
Eu usei shlex, mas ainda assim o problema persiste, matar não está matando os processos filho.
hungryWolf
1

o que eu sinto que poderíamos usar:

import os
import signal
import subprocess
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)

os.killpg(os.getpgid(pro.pid), signal.SIGINT)

isso não matará toda a sua tarefa, mas o processo com o p.pid

Nilesh K.
fonte
0

Sei que essa é uma pergunta antiga, mas isso pode ajudar alguém que procura um método diferente. É isso que eu uso no Windows para matar processos que chamei.

si = subprocess.STARTUPINFO()
si.dwFlags |= subprocess.STARTF_USESHOWWINDOW
subprocess.call(["taskkill", "/IM", "robocopy.exe", "/T", "/F"], startupinfo=si)

/ IM é o nome da imagem, você também pode fazer / PID, se quiser. / T mata o processo e os processos filho. / F force termina. si, como eu o defini, é como você faz isso sem mostrar uma janela CMD. Este código é usado no python 3.

Zx4161
fonte
0

Envie o sinal para todos os processos em grupo

    self.proc = Popen(commands, 
            stdout=PIPE, 
            stderr=STDOUT, 
            universal_newlines=True, 
            preexec_fn=os.setsid)

    os.killpg(os.getpgid(self.proc.pid), signal.SIGHUP)
    os.killpg(os.getpgid(self.proc.pid), signal.SIGTERM)
rogers.wang
fonte
0

Eu não vi isso mencionado aqui, por isso estou colocando lá fora, caso alguém precise. Se tudo o que você deseja é garantir que seu subprocesso seja encerrado com êxito, você pode colocá-lo em um gerenciador de contexto. Por exemplo, eu queria que minha impressora padrão imprimisse uma imagem e o uso do gerenciador de contexto garantisse que o subprocesso fosse encerrado.

import subprocess

with open(filename,'rb') as f:
    img=f.read()
with subprocess.Popen("/usr/bin/lpr", stdin=subprocess.PIPE) as lpr:
    lpr.stdin.write(img)
print('Printed image...')

Eu acredito que esse método também é multiplataforma.

Jaiyam Sharma
fonte
Não consigo ver como o código aqui encerra um processo. Você pode esclarecer?
DarkLight 12/09/19
Afirmei claramente que, se tudo o que você deseja fazer é garantir que o processo seja encerrado antes de prosseguir para a próxima linha do seu código, você poderá usar esse método. Não afirmei que encerra o processo.
Jaiyam Sharma 13/09/19
Se o gerente de contexto "garante que seu processo tenha terminado", como já afirmou claramente, então você fazer alegação de que ele termina o processo. No entanto? Mesmo quando o processo é iniciado shell=True, conforme a pergunta? O qual não está no seu código.
anon
anon, obrigado por apontar o uso confuso da palavra 'encerrado'. O gerenciador de contexto aguarda a conclusão do subprocesso antes de passar para a instrução de impressão na última linha. Isso é diferente de encerrar o processo instantaneamente usando algo como um sigterm. Você pode ter o mesmo resultado usando um thread e uma chamada thread.join(). Espero que esteja entendido.
Jaiyam Sharma 12/04