Para expandir um pouco as respostas anteriores aqui, há vários detalhes que são geralmente ignorados.
- Prefiro
subprocess.run()mais subprocess.check_call()e amigos mais subprocess.call()sobre subprocess.Popen()mais os.system()sobreos.popen()
- Entenda e provavelmente use
text=True, aka universal_newlines=True.
- Entenda o significado de
shell=Trueou shell=Falsee como ele muda entre aspas e a disponibilidade de conveniências de shell.
- Entenda as diferenças entre
she Bash
- Entenda como um subprocesso é separado de seu pai e geralmente não pode alterar o pai.
- Evite executar o interpretador Python como um subprocesso do Python.
Esses tópicos são abordados com mais detalhes abaixo.
Preferir subprocess.run()ousubprocess.check_call()
o subprocess.Popen() função é uma força de trabalho de baixo nível, mas é difícil de usar corretamente e você acaba copiando / colando várias linhas de código ... que já existem convenientemente na biblioteca padrão como um conjunto de funções de wrapper de nível superior para várias finalidades, que são apresentados com mais detalhes a seguir.
Aqui está um parágrafo da documentação :
A abordagem recomendada para chamar subprocessos é usar a run()função para todos os casos de uso que ela puder manipular. Para casos de uso mais avançados, a Popeninterface subjacente pode ser usada diretamente.
Infelizmente, a disponibilidade dessas funções do wrapper difere entre as versões do Python.
subprocess.run()foi introduzido oficialmente no Python 3.5. Ele pretende substituir todos os itens a seguir.
subprocess.check_output()foi introduzido no Python 2.7 / 3.1. É basicamente equivalente asubprocess.run(..., check=True, stdout=subprocess.PIPE).stdout
subprocess.check_call()foi introduzido no Python 2.5. É basicamente equivalente asubprocess.run(..., check=True)
subprocess.call()foi introduzido no Python 2.4 no subprocessmódulo original ( PEP-324 ). É basicamente equivalente asubprocess.run(...).returncode
API de alto nível vs subprocess.Popen()
O refatorado e estendido subprocess.run()é mais lógico e mais versátil do que as funções herdadas mais antigas que ele substitui. Ele retorna um CompletedProcessobjeto que possui vários métodos que permitem recuperar o status de saída, a saída padrão e alguns outros resultados e indicadores de status do subprocesso concluído.
subprocess.run()é o caminho a percorrer se você simplesmente precisar de um programa para executar e retornar o controle ao Python. Para cenários mais envolvidos (processos em segundo plano, talvez com E / S interativa com o programa pai Python), você ainda precisa usar subprocess.Popen()e cuidar de todo o encanamento. Isso requer uma compreensão bastante complexa de todas as partes móveis e não deve ser realizada de ânimo leve. O Popenobjeto mais simples representa o processo (possivelmente ainda em execução) que precisa ser gerenciado a partir do seu código pelo restante da vida útil do subprocesso.
Talvez deva ser enfatizado que apenas subprocess.Popen()cria um processo. Se você deixar assim, você tem um subprocesso em execução simultaneamente com o Python, portanto, um processo "em segundo plano". Se ele não precisar fazer entrada ou saída ou coordenar-se com você, poderá fazer um trabalho útil em paralelo com o seu programa Python.
Evite os.system()eos.popen()
Desde o tempo eterno (bem, desde Python 2.5) a osdocumentação do módulo tem contido a recomendação de preferência subprocesssobre os.system():
O subprocessmódulo fornece instalações mais poderosas para gerar novos processos e recuperar seus resultados; usar esse módulo é preferível a usar esta função.
O problema system()é que, obviamente, depende do sistema e não oferece maneiras de interagir com o subprocesso. Ele simplesmente roda, com saída padrão e erro padrão fora do alcance do Python. A única informação que Python recebe de volta é o status de saída do comando (zero significa sucesso, embora o significado de valores diferentes de zero também dependa um pouco do sistema).
O PEP-324 (que já foi mencionado acima) contém uma justificativa mais detalhada sobre por que os.systemé problemático e como subprocesstenta resolver esses problemas.
os.popen()costumava ser ainda mais fortemente desencorajado :
Descontinuado desde a versão 2.6: Esta função está obsoleta. Use o subprocessmódulo
No entanto, desde algum momento no Python 3, ele foi reimplementado para simplesmente usar subprocesse redireciona para a subprocess.Popen()documentação para obter detalhes.
Entenda e use normalmente check=True
Você também notará que subprocess.call()possui muitas das mesmas limitações que os.system(). Em uso regular, você geralmente deve verificar se o processo foi concluído com êxito, qual subprocess.check_call()e o que subprocess.check_output()faz (onde o último também retorna a saída padrão do subprocesso concluído). Da mesma forma, você deve usar normalmente, check=Truea subprocess.run()menos que precise especificamente permitir que o subprocesso retorne um status de erro.
Na prática, com check=Trueou subprocess.check_*, Python lançará uma CalledProcessErrorexceção se o subprocesso retornar um status de saída diferente de zero.
Um erro comum subprocess.run()é omitir check=Truee surpreender-se quando o código downstream falhar se o subprocesso falhar.
Por outro lado, um problema comum check_call()e check_output()era que os usuários que usavam cegamente essas funções ficaram surpresos quando a exceção foi levantada, por exemplo, quando grepnão encontraram uma correspondência. (Você provavelmente deve substituir o grepcódigo Python nativo de qualquer maneira, conforme descrito abaixo.)
Tudo contado, você precisa entender como os comandos do shell retornam um código de saída e sob quais condições eles retornarão um código de saída diferente de zero (erro) e tomarão uma decisão consciente de como exatamente deve ser tratado.
Entenda e provavelmente use text=Trueakauniversal_newlines=True
Desde Python 3, cadeias internas ao Python são cadeias Unicode. Mas não há garantia de que um subprocesso gere saída Unicode ou seqüências de caracteres.
(Se as diferenças não forem imediatamente óbvias, recomenda-se a leitura pragmática do Node Batchelder Unicode , se não for totalmente obrigatória. Há uma apresentação em vídeo de 36 minutos por trás do link, se você preferir, embora a leitura da página provavelmente leve muito menos tempo. )
No fundo, o Python precisa buscar um bytesbuffer e interpretá-lo de alguma forma. Se ele contém um blob de dados binários, não deve ser decodificado em uma seqüência de caracteres Unicode, porque é um comportamento propenso a erros e indutor de erros - precisamente o tipo de comportamento irritante que envolveu muitos scripts Python 2, antes que houvesse uma maneira de distinguir adequadamente entre texto codificado e dados binários.
Com text=True, você diz ao Python que, na verdade, espera dados de texto de volta na codificação padrão do sistema e que eles devem ser decodificados em uma string Python (Unicode) da melhor maneira possível (geralmente UTF-8 em qualquer moderadamente) sistema de datas, exceto talvez o Windows?)
Se isso é não o que você pedir de volta, Python vai apenas dar-lhe bytescordas nos stdoute stderrcordas. Talvez em algum depois apontar-lhe que sei que eles eram cadeias de texto depois de tudo, e você sabe sua codificação. Então, você pode decodificá-los.
normal = subprocess.run([external, arg],
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
check=True,
text=True)
print(normal.stdout)
convoluted = subprocess.run([external, arg],
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
check=True)
# You have to know (or guess) the encoding
print(convoluted.stdout.decode('utf-8'))
O Python 3.7 introduziu o alias mais curto, descritivo e compreensível textpara o argumento de palavra-chave, que antes era chamado de maneira enganosa universal_newlines.
Understand shell=Truevsshell=False
Com shell=Truevocê, você passa uma única string para o seu shell, e o shell o leva a partir daí.
Com shell=Falsevocê, passe uma lista de argumentos para o sistema operacional, ignorando o shell.
Quando você não possui um shell, salva um processo e se livra de uma quantidade bastante substancial de complexidade oculta, que pode ou não abrigar bugs ou mesmo problemas de segurança.
Por outro lado, quando você não possui um shell, não possui redirecionamento, expansão de curinga, controle de tarefas e um grande número de outros recursos do shell.
Um erro comum é usar shell=Truee ainda transmitir ao Python uma lista de tokens, ou vice-versa. Isso acontece em alguns casos, mas é realmente mal definido e pode ser interrompido de maneiras interessantes.
# XXX AVOID THIS BUG
buggy = subprocess.run('dig +short stackoverflow.com')
# XXX AVOID THIS BUG TOO
broken = subprocess.run(['dig', '+short', 'stackoverflow.com'],
shell=True)
# XXX DEFINITELY AVOID THIS
pathological = subprocess.run(['dig +short stackoverflow.com'],
shell=True)
correct = subprocess.run(['dig', '+short', 'stackoverflow.com'],
# Probably don't forget these, too
check=True, text=True)
# XXX Probably better avoid shell=True
# but this is nominally correct
fixed_but_fugly = subprocess.run('dig +short stackoverflow.com',
shell=True,
# Probably don't forget these, too
check=True, text=True)
A réplica comum "mas funciona para mim" não é uma refutação útil, a menos que você entenda exatamente sob quais circunstâncias ela poderia parar de funcionar.
Exemplo de refatoração
Muitas vezes, os recursos do shell podem ser substituídos pelo código Python nativo. O Awk ou sedscripts simples provavelmente devem ser traduzidos simplesmente para Python.
Para ilustrar isso parcialmente, aqui está um exemplo típico, mas um pouco tolo, que envolve muitos recursos de shell.
cmd = '''while read -r x;
do ping -c 3 "$x" | grep 'round-trip min/avg/max'
done <hosts.txt'''
# Trivial but horrible
results = subprocess.run(
cmd, shell=True, universal_newlines=True, check=True)
print(results.stdout)
# Reimplement with shell=False
with open('hosts.txt') as hosts:
for host in hosts:
host = host.rstrip('\n') # drop newline
ping = subprocess.run(
['ping', '-c', '3', host],
text=True,
stdout=subprocess.PIPE,
check=True)
for line in ping.stdout.split('\n'):
if 'round-trip min/avg/max' in line:
print('{}: {}'.format(host, line))
Algumas coisas a serem observadas aqui:
- Com
shell=Falsevocê, não é necessário citar que o shell requer em torno de strings. Colocar aspas de qualquer maneira provavelmente é um erro.
- Geralmente, faz sentido executar o mínimo de código possível em um subprocesso. Isso lhe dá mais controle sobre a execução de dentro do seu código Python.
- Dito isto, pipelines de shell complexos são tediosos e, às vezes, difíceis de reimplementar em Python.
O código refatorado também ilustra o quanto o shell realmente faz por você com uma sintaxe muito concisa - para melhor ou para pior. Python diz que explícito é melhor que implícito, mas o código Python é bastante detalhado e sem dúvida parece mais complexo do que isso realmente é. Por outro lado, oferece vários pontos onde você pode obter o controle no meio de outra coisa, como exemplifica trivialmente o aprimoramento de que podemos incluir facilmente o nome do host junto com a saída do comando shell. (Isso também não é difícil de fazer no shell, mas às custas de mais um desvio e talvez outro processo.)
Construções comuns do shell
Para completar, aqui estão breves explicações sobre alguns desses recursos do shell e algumas notas sobre como eles podem ser substituídos por recursos nativos do Python.
- A expansão de curingas, também conhecida como globbing, pode ser substituída
glob.glob()ou frequentemente por comparações simples de strings do Python, como for file in os.listdir('.'): if not file.endswith('.png'): continue. O Bash possui vários outros recursos de expansão, como .{png,jpg}expansão de chaves e {1..100}também a expansão de til ( ~expande para o diretório inicial e, geralmente, ~accountpara o diretório inicial de outro usuário)
- Variáveis de shell como
$SHELLou $my_exported_varàs vezes podem simplesmente ser substituídas por variáveis Python. Variáveis do shell exportados estão disponíveis como por exemplo os.environ['SHELL'](o significado exporté fazer com que a variável disponível para subprocessos -. Uma variável que não está disponível para subprocessos, obviamente, não estará disponível para Python executado como um subprocesso da casca, ou vice-versa A env=palavra-chave O argumento para subprocessmétodos permite definir o ambiente do subprocesso como um dicionário; portanto, é uma maneira de tornar uma variável Python visível para um subprocesso). Com shell=Falsevocê precisará entender como remover quaisquer aspas; por exemplo, cd "$HOME"é equivalente a os.chdir(os.environ['HOME'])sem aspas ao redor do nome do diretório. (Muitas vezescdde qualquer maneira, não é útil ou necessário, e muitos iniciantes omitem as aspas duplas em torno da variável e ficam impunes até um dia ... )
- O redirecionamento permite que você leia um arquivo como entrada padrão e grave sua saída padrão em um arquivo.
grep 'foo' <inputfile >outputfileabre outputfilepara escrever e inputfilepara ler e passa seu conteúdo como entrada padrão para grep, cuja saída padrão então chega outputfile. Isso geralmente não é difícil de substituir pelo código Python nativo.
- Pipelines são uma forma de redirecionamento.
echo foo | nlexecuta dois subprocessos, em que a saída padrão de echoé a entrada padrão de nl(no nível do SO, em sistemas semelhantes ao Unix, esse é um identificador de arquivo único). Se você não pode substituir uma ou as duas extremidades do pipeline pelo código Python nativo, talvez pense em usar um shell, afinal, especialmente se o pipeline tiver mais de dois ou três processos (embora veja o pipesmódulo na biblioteca padrão do Python ou vários de concorrentes de terceiros mais modernos e versáteis).
- O controle de tarefas permite interromper as tarefas, executá-las em segundo plano, devolvê-las ao primeiro plano etc. Os sinais básicos do Unix para parar e continuar um processo também estão disponíveis no Python. Mas os trabalhos são uma abstração de nível superior no shell, que envolve grupos de processos, etc., que você precisa entender se quiser fazer algo assim no Python.
- Citar no shell é potencialmente confuso até você entender que tudo é basicamente uma string. Portanto,
ls -l /é equivalente a 'ls' '-l' '/', mas os citando torno literais é completamente opcional. Seqüências de caracteres não citadas que contêm metacaracteres de shell sofrem expansão de parâmetro, tokenização de espaço em branco e expansão de curinga; aspas duplas impedem a tokenização de espaço em branco e a expansão de curingas, mas permitem expansões de parâmetros (substituição de variável, substituição de comando e processamento de barra invertida). Isso é simples em teoria, mas pode ser desconcertante, especialmente quando há várias camadas de interpretação (um comando de shell remoto, por exemplo).
Entenda as diferenças entre she Bash
subprocessexecuta seus comandos shell, a /bin/shmenos que você solicite especificamente o contrário (exceto, é claro, no Windows, onde ele usa o valor da COMSPECvariável). Isso significa que vários recursos exclusivos do Bash, como matrizes, [[etc. não estão disponíveis.
Se você precisar usar a sintaxe apenas do Bash, poderá passar o caminho para o shell como executable='/bin/bash'(onde, é claro, se o seu Bash estiver instalado em outro lugar, será necessário ajustar o caminho).
subprocess.run('''
# This for loop syntax is Bash only
for((i=1;i<=$#;i++)); do
# Arrays are Bash-only
array[i]+=123
done''',
shell=True, check=True,
executable='/bin/bash')
A subprocessé separado de seu pai e não pode alterá-lo
Um erro um tanto comum é fazer algo como
subprocess.run('foo=bar', shell=True)
subprocess.run('echo "$foo"', shell=True) # Doesn't work
que, além da falta de elegância, também trai uma falta fundamental de compreensão da parte "sub" do nome "subprocesso".
Um processo filho é executado completamente separado do Python e, quando termina, o Python não tem idéia do que fez (além dos indicadores vagos que podem inferir do status de saída e saída do processo filho). Uma criança geralmente não pode mudar o ambiente dos pais; não pode definir uma variável, alterar o diretório de trabalho ou, em muitas palavras, se comunicar com seu pai sem a cooperação do pai.
A correção imediata nesse caso específico é executar os dois comandos em um único subprocesso;
subprocess.run('foo=bar; echo "$foo"', shell=True)
embora obviamente esse caso de uso específico não exija o shell. Lembre-se, você pode manipular o ambiente do processo atual (e, portanto, também seus filhos) via
os.environ['foo'] = 'bar'
ou passar uma configuração de ambiente para um processo filho com
subprocess.run('echo "$foo"', shell=True, env={'foo': 'bar'})
(para não mencionar a refatoração óbvia subprocess.run(['echo', 'bar']); mas echoé um mau exemplo de algo para executar em um subprocesso, é claro).
Não execute o Python a partir do Python
Este é um conselho um pouco dúbio; Certamente, há situações em que faz sentido ou é mesmo um requisito absoluto executar o interpretador Python como um subprocesso de um script Python. Mas com muita frequência, a abordagem correta é simplesmente para importo outro módulo Python no seu script de chamada e chamar suas funções diretamente.
Se o outro script Python estiver sob seu controle e não for um módulo, considere transformá-lo em um . (Essa resposta já é muito longa, portanto não vou me aprofundar nos detalhes aqui.)
Se você precisar de paralelismo, poderá executar funções Python em subprocessos com o multiprocessingmódulo. Também existe a threadingexecução de várias tarefas em um único processo (que é mais leve e oferece mais controle, mas também mais restrito, pois os segmentos de um processo são fortemente acoplados e vinculados a um único GIL .)
cwm. Talvez você tenha alguma configuração.bashrcque configure o ambiente para o uso do bash interativo?