Executar Comandos Externos em Python com o Módulo subprocess
Aprenda a executar comandos externos em Python com o módulo subprocess da biblioteca padrão: subprocess.run, capturar saída, pipes, encoding UTF-8 e cp1252, timeouts, tratamento de erros, segurança contra shell injection e casos de uso brasileiros como converter documentos no LibreOffice, OCR com Tesseract e ffmpeg. Guia prático em português.
O subprocess é o módulo da biblioteca padrão do Python que executa comandos externos — qualquer programa instalado no sistema operacional, do git ao libreoffice, passando por ffmpeg, tesseract, pdftk e ferramentas próprias da sua empresa. Para quem automatiza rotinas no Brasil, ele é a ponte entre o Python e o ecossistema de linha de comando que já existe nos servidores e estações de trabalho: converter um .docx em PDF, fazer OCR de uma nota fiscal escaneada, compactar um vídeo, disparar um git pull antes de um deploy. Neste tutorial você vai aprender a usar o subprocess com exemplos práticos, tratamento de erros robusto e os cuidados de segurança que diferenciam um script que funciona de um script que vira vulnerabilidade.
Se você já automatiza planilhas, gera relatórios em PDF ou edita documentos do Word, o subprocess abre um novo nível de automação: em vez de reimplementar tudo em Python puro, você orquestra as ferramentas certas para cada tarefa. Quem está começando pode revisar o guia de Python para iniciantes e a introdução à automação com Python; aqui o foco é executar processos externos com segurança e controle.
A regra de ouro: use subprocess.run
A API moderna e recomendada é a função subprocess.run, disponível desde o Python 3.5. Ela executa o comando, espera terminar e devolve um objeto CompletedProcess com tudo o que você precisa:
import subprocess
resultado = subprocess.run(["echo", "Olá, Brasil!"], capture_output=True, text=True)
print(resultado.stdout) # "Olá, Brasil!\n"
print(resultado.returncode) # 0 (sucesso)
Esqueça o antigo os.system: ele só devolve o código de saída numérico, não captura a saída e é menos seguro. O subprocess.run é o substituto direto e superior. Outras funções do módulo (call, check_output, getoutput) ainda existem por compatibilidade, mas em código novo run cobre todos os casos.
Argumentos como lista, não como string
O ponto que mais confunde iniciantes: o primeiro argumento de run deve ser uma lista, em que cada elemento é um argumento separado.
# Correto: cada argumento é um item da lista
subprocess.run(["ls", "-l", "/tmp"])
# Errado (e perigoso): uma string só funciona com shell=True
subprocess.run("ls -l /tmp", shell=True) # evite!
Passar a lista faz o Python invocar o programa diretamente, sem acionar um shell intermediário. Isso é mais rápido, mais previsível e — o mais importante — mais seguro, como veremos adiante.
Capturando saída e código de retorno
Para ler o que o comando escreveu, use capture_output=True junto com text=True. Sem text=True, a saída vem como bytes; com ele, o Python decodifica para str usando o encoding padrão do sistema.
resultado = subprocess.run(
["git", "log", "--oneline", "-5"],
capture_output=True,
text=True,
)
if resultado.returncode == 0:
print(resultado.stdout)
else:
print("Erro:", resultado.stderr)
O objeto devolvido tem quatro atributos principais:
stdout: a saída padrão (o “resultado normal” do comando).stderr: a saída de erro (avisos, diagnósticos, mensagens de falha).returncode:0indica sucesso; valores diferentes de zero indicam erro.args: a lista de argumentos que foi passada.
Para uma referência rápida de como tratar essas falhas, vale conferir o artigo sobre tratamento de erros em Python.
A pegadinha brasileira: encoding UTF-8 x cp1252
Aqui está um problema clássico para quem trabalha no Brasil. No Linux e no macOS, a saída dos comandos costuma vir em UTF-8. No Windows em português, muitos programas devolvem texto em cp1252 (Windows-1252), e o encoding padrão do Python no Windows é justamente o cp1252 — o que pode bagunçar acentos quando o texto é UTF-8, ou vice-versa.
A solução é declarar o encoding explicitamente em vez de depender do padrão do sistema:
# Tenta UTF-8 primeiro; recua para cp1252 se houver erro de decodificação
try:
resultado = subprocess.run(
["meu_programa.exe"],
capture_output=True,
text=True,
encoding="utf-8",
)
saida = resultado.stdout
except UnicodeDecodeError:
resultado = subprocess.run(
["meu_programa.exe"],
capture_output=True,
)
saida = resultado.stdout.decode("cp1252")
Uma alternativa robusta para origem desconhecida é capturar como bytes (sem text=True) e decodificar com errors="replace", que substitui caracteres inválidos em vez de quebrar.
Validação automática com check=True
Em vez de testar returncode == 0 manualmente, passe check=True: se o comando falhar, o Python levanta a exceção subprocess.CalledProcessError, que você captura com try/except.
import subprocess
try:
subprocess.run(
["git", "push"],
check=True,
capture_output=True,
text=True,
)
print("Deploy enviado com sucesso.")
except subprocess.CalledProcessError as erro:
print(f"Falha (código {erro.returncode}): {erro.stderr}")
Isso torna o fluxo de erro explícito e deixa o código mais limpo — especialmente em pipelines que encadeiam várias etapas, como um deploy de aplicação Python.
Timeout: nunca confie num comando externo
Comandos externos podem travar — um download que nunca termina, um processo que espera entrada que não chega, um servidor que não responde. Sempre que há risco, defina um timeout (em segundos):
try:
resultado = subprocess.run(
["curl", "-s", "https://api.exemplo.com/saude"],
capture_output=True,
text=True,
timeout=10,
)
except subprocess.TimeoutExpired:
print("O serviço demorou mais de 10 segundos — abortado.")
Quando o prazo expira, o Python mata o processo e levanta TimeoutExpired. Em scripts de automação que rodam em cron ou em pipelines orquestrados com Airflow, o timeout é o que separa uma falha limpa de um job pendurado para sempre.
Entrada de dados via input
Para enviar dados para a entrada padrão (stdin) de um comando, use o argumento input:
resultado = subprocess.run(
["python", "-c", "print(input().upper())"],
input="olá, brasil",
capture_output=True,
text=True,
encoding="utf-8",
)
print(resultado.stdout.strip()) # "OLÁ, BRASIL"
Isso substitui o uso frágil de arquivos temporários e é ideal para comandos que esperam texto na entrada.
Encadeando processos com Popen
Quando você precisa conectar a saída de um comando à entrada de outro (o famoso pipe | do shell) ou controlar o processo sem bloquear, use a classe subprocess.Popen:
proc_1 = subprocess.Popen(["ls", "-1"], stdout=subprocess.PIPE)
proc_2 = subprocess.Popen(["wc", "-l"], stdin=proc_1.stdout, stdout=subprocess.PIPE)
proc_1.stdout.close() # permite que proc_1 receba SIGPIPE se proc_2 terminar
saida, _ = proc_2.communicate()
print(int(saida)) # número de arquivos no diretório
O Popen é a base de baixo nível sobre a qual o próprio run é construído. Use-o quando run não for suficiente — por exemplo, para lançar um processo em segundo plano e continuar executando o seu script:
servidor = subprocess.Popen(["python", "-m", "http.server", "8000"])
# o servidor continua rodando; o script segue em frente
print("Servidor iniciado em segundo plano.")
# depois, quando quiser parar:
servidor.terminate()
Para concorrência real entre tarefas pesadas em Python, o caminho certo é o módulo multiprocessing ou a programação assíncrona com async/await; o subprocess orquestra processos externos, não substitui o paralelismo interno.
Segurança: o perigo do shell=True
A regra mais importante deste tutorial: evite shell=True. Quando você passa shell=True, o Python monta uma string e a entrega a um shell (/bin/sh no Linux, cmd.exe no Windows), que interpreta caracteres especiais como ;, |, && e $(). Se algum desses caracteres vier de um dado externo — um nome de arquivo enviado pelo usuário, um campo de um formulário —, você abre um buraco de injeção de comandos (shell injection).
# PERIGOSO: se nome_arquivo vier do usuário e contiver
# "; rm -rf ~", o shell executa a remoção
subprocess.run(f"convert {nome_arquivo} saida.png", shell=True)
# SEGURO: a lista isola cada argumento; não há shell para interpretar
subprocess.run(["convert", nome_arquivo, "saida.png"])
Na forma de lista, o nome_arquivo é sempre tratado como um argumento literal, por mais malicioso que seja o conteúdo. Se você realmente precisa de recursos do shell (curingas *, redirecionamento >, encadeamento &&), construa a string a partir de dados confiáveis e nunca de entrada do usuário — e ainda assim prefira resolver a lógica em Python.
Casos de uso brasileiros na prática
O subprocess brilha em automações que combinam o Python com ferramentas de linha de comando consagradas. Alguns cenários comuns no mercado brasileiro:
Converter documentos no LibreOffice. Muitas empresas têm o LibreOffice instalado em servidores Linux. Com o modo headless, você converte .docx, .xlsx e .odt para PDF sem abrir interface gráfica — uma mão na roda para fechar o ciclo de geração de documentos e relatórios:
subprocess.run(
["libreoffice", "--headless", "--convert-to", "pdf", "--outdir", "saida", "contrato.docx"],
check=True,
capture_output=True,
text=True,
timeout=60,
)
OCR de documentos com Tesseract. Para ler notas fiscais, comprovantes e boletos escaneados, o Tesseract com o pacote de idioma português (por) é o motor de OCR mais acessível:
subprocess.run(
["tesseract", "nota_fiscal.png", "saida", "-l", "por"],
check=True,
capture_output=True,
text=True,
timeout=30,
)
# o texto fica em saida.txt
Compactar e converter mídia com ffmpeg. Redimensionar vídeos ou extrair áudio de um podcast antes de publicar:
subprocess.run(
["ffmpeg", "-y", "-i", "entrada.mp4", "-c:v", "libx265", "-crf", "28", "saida.mp4"],
check=True,
timeout=300,
)
Automação de Git. Disparar um commit e push ao final de um processamento, conectando-se ao tema de automação com Git em Python:
subprocess.run(["git", "add", "-A"], check=True)
subprocess.run(["git", "commit", "-m", "Atualização automática diária"], check=True)
subprocess.run(["git", "push"], check=True, capture_output=True, text=True)
Boas práticas que evitam horas de debug
- Prefira
subprocess.rune passe argumentos como lista. - Nunca use
shell=Truecom dados externos; o risco de injeção é real. - Declare o
encodingexplicitamente (utf-8oucp1252) para não depender do padrão do sistema. - Sempre defina um
timeoutquando o comando puder travar. - Use
check=Truepara que falhas virem exceções em vez de código silencioso. - Registre a saída e o código de retorno com o módulo de logging em automações de longa duração.
- Trate caminhos de arquivo com
pathlibe valide entradas externas antes de passá-las a comandos.
Com o subprocess dominado, você transforma o Python em um maestro capaz de reger qualquer ferramenta de linha de comando — do LibreOffice ao Tesseract, do ffmpeg ao git —, encadeando etapas de automação que respeitam encoding, tratam falhas e rodam com a segurança que a produção exige.
Equipe Python Brasil
Contribuidor do Python Brasil — Aprenda Python em Português