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=True
ou shell=False
e como ele muda entre aspas e a disponibilidade de conveniências de shell.
- Entenda as diferenças entre
sh
e 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 Popen
interface 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 subprocess
mó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 CompletedProcess
objeto 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 Popen
objeto 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 os
documentação do módulo tem contido a recomendação de preferência subprocess
sobre os.system()
:
O subprocess
mó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 subprocess
tenta 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 subprocess
módulo
No entanto, desde algum momento no Python 3, ele foi reimplementado para simplesmente usar subprocess
e 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=True
a subprocess.run()
menos que precise especificamente permitir que o subprocesso retorne um status de erro.
Na prática, com check=True
ou subprocess.check_*
, Python lançará uma CalledProcessError
exceção se o subprocesso retornar um status de saída diferente de zero.
Um erro comum subprocess.run()
é omitir check=True
e 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 grep
não encontraram uma correspondência. (Você provavelmente deve substituir o grep
có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=True
akauniversal_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 bytes
buffer 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 bytes
cordas nos stdout
e stderr
cordas. 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 text
para o argumento de palavra-chave, que antes era chamado de maneira enganosa universal_newlines
.
Understand shell=True
vsshell=False
Com shell=True
você, você passa uma única string para o seu shell, e o shell o leva a partir daí.
Com shell=False
você, 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=True
e 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 sed
scripts 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=False
você, 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, ~account
para o diretório inicial de outro usuário)
- Variáveis de shell como
$SHELL
ou $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 subprocess
mé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=False
você 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 vezescd
de 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 >outputfile
abre outputfile
para escrever e inputfile
para 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 | nl
executa 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 pipes
mó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 sh
e Bash
subprocess
executa seus comandos shell, a /bin/sh
menos que você solicite especificamente o contrário (exceto, é claro, no Windows, onde ele usa o valor da COMSPEC
variá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 import
o 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 multiprocessing
módulo. Também existe a threading
execuçã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.bashrc
que configure o ambiente para o uso do bash interativo?