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.

8 min de leitura Equipe Python Brasil

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: 0 indica 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.run e passe argumentos como lista.
  • Nunca use shell=True com dados externos; o risco de injeção é real.
  • Declare o encoding explicitamente (utf-8 ou cp1252) para não depender do padrão do sistema.
  • Sempre defina um timeout quando o comando puder travar.
  • Use check=True para 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 pathlib e 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.

E

Equipe Python Brasil

Contribuidor do Python Brasil — Aprenda Python em Português