subprocess inicia programas externos e conecta entrada, saída e código de retorno. A interface atravessa a fronteira entre Python e o sistema operacional, o que exige argumentos, tempo e ambiente controlados.

Usar run() com argumentos separados

import subprocess

resultado = subprocess.run(
    ["git", "rev-parse", "--short", "HEAD"],
    check=True,
    capture_output=True,
    text=True,
    timeout=10,
)

commit = resultado.stdout.strip()

Uma lista envia cada argumento separadamente e evita interpretação automática pelo shell. check=True transforma falha em exceção, timeout limita espera e text=True decodifica a saída.

Evitar injeção e ambiente imprevisível

Não concatene entrada do usuário em uma string executada com shell=True. Metacaracteres podem criar comandos adicionais. Valide valores por allowlist e passe-os como itens separados. Há particularidades para arquivos .bat e .cmd no Windows; revise a seção de segurança da documentação.

Defina cwd explicitamente quando caminhos relativos forem necessários. Passe um env controlado sem apagar variáveis essenciais e nunca inclua segredos em logs. Para saída grande, use Popen.communicate() ou arquivos, evitando leituras manuais que podem bloquear pipes.

Bandit pode sinalizar padrões perigosos, como mostra o guia de análise de segurança com Bandit. Ainda assim, a revisão precisa entender de onde vêm os argumentos.

A documentação oficial de subprocess, consultada em 22 de julho de 2026, recomenda run() nos casos que ele atende e detalha timeout, pipes e segurança. Teste também falha, timeout e executável ausente.

Separar executável e argumentos

Com shell=False, o Python inicia o programa diretamente. Cada item da lista vira um argumento, então espaços no nome de arquivo não o dividem e metacaracteres não ganham significado de shell. Não use command.split(): regras de aspas variam por plataforma. Construa a lista com partes conhecidas.

Separação não elimina a validação. Um valor iniciado por - pode virar opção do programa, e um caminho irrestrito pode expor arquivo indevido. Mapeie escolhas públicas para argumentos internos:

FORMATOS = {
    "curto": ["--format", "short"],
    "json": ["--format", "json"],
}

def gerar_relatorio(formato: str) -> str:
    if formato not in FORMATOS:
        raise ValueError("formato não suportado")
    resultado = subprocess.run(
        ["/opt/acme/bin/report", *FORMATOS[formato]],
        check=True,
        capture_output=True,
        text=True,
        timeout=15,
    )
    return resultado.stdout

Um caminho absoluto não depende de PATH. Quando a descoberta for intencional, use shutil.which() e valide o resultado.

Tratar as falhas esperadas

check=True gera CalledProcessError para status não zero. Executável ausente gera FileNotFoundError; limite vencido gera TimeoutExpired:

try:
    concluido = subprocess.run(
        ["git", "status", "--porcelain=v1"],
        check=True,
        capture_output=True,
        text=True,
        timeout=5,
    )
except subprocess.TimeoutExpired as exc:
    raise RuntimeError("git excedeu o tempo") from exc
except subprocess.CalledProcessError as exc:
    raise RuntimeError(f"git terminou com status {exc.returncode}") from exc
except FileNotFoundError as exc:
    raise RuntimeError("git não está instalado") from exc

Não devolva stderr bruto ao navegador: ele pode conter caminhos, ambiente ou tokens. Registre diagnóstico sanitizado com acesso controlado e entregue mensagem estável. Alguns programas usam status não zero para “diferenças encontradas”; consulte o contrato antes de usar check=True.

Limitar tempo, saída e entrada

timeout limita a espera, embora a criação do processo possa não ser interrompível em toda plataforma. Ao expirar, run() mata o filho, aguarda e gera TimeoutExpired. Teste com a ferramenta real, sobretudo se ela iniciar descendentes.

capture_output=True guarda os dois fluxos na memória. É adequado para respostas pequenas, não para backup ou vídeo. Redirecione saída grande para arquivo já aberto:

from pathlib import Path

destino = Path("/var/lib/acme/relatorio.json")
with destino.open("wb") as saida:
    subprocess.run(
        ["/opt/acme/bin/report", "--json"],
        stdout=saida,
        stderr=subprocess.PIPE,
        check=True,
        timeout=60,
    )

Com Popen, ler stdout e ignorar um stderr cheio pode bloquear. Prefira communicate() salvo quando houver streaming cuidadosamente testado. Não aceite entrada, duração ou captura ilimitadas em uma requisição web.

Passe conteúdo por input=, não pela linha de comando, que costuma aparecer em listagens:

resultado = subprocess.run(
    ["/usr/bin/tool", "--read-stdin"],
    input=conteudo,
    text=True,
    capture_output=True,
    check=True,
    timeout=10,
)

Para segredos, prefira mecanismo protegido oferecido pelo programa. Variáveis de ambiente são menos visíveis que argumentos, mas não formam uma fronteira universal.

Controlar diretório e ambiente

Caminhos relativos dependem de cwd; comportamento herdado depende de env. Use um diretório confiável e resolvido. Nunca permita que entrada externa escolha livremente o diretório de trabalho.

Passar env substitui todo o ambiente do filho. Comece com os.environ.copy() se ele precisar das variáveis normais e sobrescreva apenas as desejadas. Para isolamento maior, crie ambiente mínimo. Remova variáveis perigosas de loader ou linguagem ao iniciar ferramentas privilegiadas e nunca registre o mapa completo.

Use encoding="utf-8" e errors="strict" se a ferramenta garantir UTF-8. Apenas text=True usa o encoding da localidade, que pode variar. Para bytes arbitrários, permaneça no modo binário.

Reservar o shell para sintaxe de shell

Pipelines, redirecionamento, curingas e comandos internos podem exigir shell. Prefira redirecionar com stdin e stdout, e formar pipelines com objetos Popen. Se o shell for indispensável, mantenha a string constante e impeça dados externos de entrar nela. shlex.quote() é voltado a shells POSIX e não sanitiza de forma portátil o Windows.

Arquivos batch no Windows podem envolver parsing do sistema mesmo com shell=False. Siga a nota de segurança oficial e teste espaços e metacaracteres; não presuma que o comportamento do Linux se repete.

Processos longos e testes

Use Popen quando precisar transmitir dados, enviar sinais ou administrar o filho. Utilize-o como gerenciador de contexto, chame communicate() para recolher o processo e defina o desligamento de descendentes. Não inicie trabalho destacado em requisição web sem uma fila supervisionada que cuide de ciclo de vida, repetição, logs e limites.

Teste sucesso, status documentados, executável ausente, timeout, encoding inválido, stderr grande, nomes com espaços e argumentos com aparência hostil. Verifique que cada valor permanece um argumento. Mocks cobrem ramos da aplicação, mas um teste de integração deve executar a ferramenta real.

O padrão seguro combina executável fixo, lista explícita e validada, shell=False, tempo e saída limitados, ambiente deliberado e status tratado. Isso torna o processo previsível sem confundir separação de argumentos com validação da semântica do programa.

Revisão operacional

Antes da implantação, confira permissões dos arquivos e a conta do sistema usada pelo filho. subprocess não cria uma sandbox: o programa herda as capacidades efetivas do pai, salvo restrições externas. Execute a aplicação com privilégio mínimo, mantenha diretórios graváveis restritos e aplique controles do sistema para CPU, memória e quantidade de processos ao executar ferramentas custosas.

Defina também o contrato de observabilidade. Registre nome lógico da operação, duração, status e ocorrência de timeout, mas não a linha de comando inteira quando houver dados sensíveis. Use um identificador de correlação para relacionar o processo à requisição. Métricas de duração e falha ajudam a detectar uma atualização do executável que ficou lenta sem expor seu conteúdo.

Se vários processos iguais podem rodar ao mesmo tempo, considere limites de concorrência. O timeout de cada execução não impede cem filhos de esgotarem o servidor. Uma fila ou semáforo oferece pressão de retorno e permite recusar excesso de trabalho de forma previsível.

Ao atualizar a ferramenta externa, valide sua versão e opções suportadas. Um argumento antes aceito pode mudar de semântica, e mensagens ou encoding podem variar. Fixar uma versão compatível e executar testes de integração reduz surpresas.