subprocess & Shell Interop
subprocess executa binários externos a partir do Python com argumentos controlados, tempos limite e saída capturada. Evite shell=True a menos que você aceite o risco de injeção e a complexidade de aspas.
Receita
import subprocess
proc = subprocess.run(
["git", "rev-parse", "HEAD"],
check=True,
capture_output=True,
text=True,
timeout=30,
)
print(proc.stdout.strip())Quando usar isso:
- Git, rsync, tar, ffmpeg e outras ferramentas CLI maduras
- Scripts shell legados que você está encapsulando incrementalmente
- Capturar stdout para análise em Python
- Definir tempo limite para comandos de longa execução para evitar travamentos em jobs cron
Exemplo de Trabalho
Executar comando com tempo limite, mapear erros para códigos de saída, transmitir saída grande linha por linha.
import logging
import subprocess
from collections.abc import Sequence
log = logging.getLogger(__name__)
def run_command(cmd: Sequence[str], timeout: float = 60) -> subprocess.CompletedProcess[str]:
log.info("exec %s", list(cmd))
try:
return subprocess.run(
cmd,
check=True,
capture_output=True,
text=True,
timeout=timeout,
)
except subprocess.TimeoutExpired as exc:
log.error("timeout após %ss: %s", timeout, cmd)
raise
except subprocess.CalledProcessError as exc:
log.error("saída %s stderr=%s", exc.returncode, exc.stderr.strip())
raise
def stream_lines(cmd: Sequence[str]) -> int:
with subprocess.Popen(cmd, stdout=subprocess.PIPE, text=True) as proc:
assert proc.stdout is not None
for line in proc.stdout:
print(line.rstrip())
return proc.wait()
if __name__ == "__main__":
result = run_command(["python3", "--version"])
print(result.stdout)O que isso demonstra:
- Sequência de argumentos, não uma única string shell - sem superfície de injeção
check=TruelevantaCalledProcessErrorcom returncode e stderrPopentransmite stdout sem armazenar toda a saída na memória
Mergulho Profundo
Escolha da API
| API | Uso |
|---|---|
run() | A maioria dos comandos - síncrono simples |
Popen() | Streaming, pipelines, interativo |
check_output() | Captura rápida quando check=True sempre |
Risco de shell=True
- Entrada do usuário na string de comando permite injeção shell
- Prefira a lista argv; se o shell for necessário, use
shlex.quoteem segmentos de usuário
Notas do Python
import shlex
user_file = "report; rm -rf /"
# RUIM: subprocess.run(f"cat {user_file}", shell=True)
subprocess.run(["cat", user_file], check=True)Armadilhas
shell=Truecom variáveis - bug clássico de injeção. Correção: lista argv oushlex.splitapenas em scripts confiáveis.- Sem tempo limite - jobs cron zumbis para sempre. Correção: sempre defina
timeoutproporcional ao tempo de execução esperado. - Capturando stdout enorme - estouro de memória. Correção: transmita com
Popenou redirecione para um arquivo temporário. - Ignorando stderr - contexto de falha oculto. Correção: registre
stderrem saída não zero. - Codificação em modo de texto - ferramentas binárias quebram. Correção:
text=Falsee decodifique explicitamente quando necessário.
Alternativas
| Alternativa | Use Quando | Não Use Quando |
|---|---|---|
| Bibliotecas Python puras | A operação tem API Python madura | CLI testada em batalha já existe |
asyncio.create_subprocess_exec | Orquestração de loop de eventos assíncrono | Script cron síncrono simples |
| Fabric/SSH remoto | Hosts remotos | Comando apenas local |
FAQs
Quando `shell=True` é aceitável?
Scripts internos corrigidos sem entrada do usuário em máquinas confiáveis - ainda assim documente a exceção e o ticket.
Como passo o ambiente?
env={**os.environ, "FOO": "bar"} - não despeje segredos no ambiente filho desnecessariamente.
Como executo em um cwd específico?
cwd="/path/to/repo" em run() - os caminhos no comando devem ser absolutos ou relativos ao cwd.
Como faço pipeline de comandos?
Encadeie dois Popen com pipe stdout->stdin, ou use pipeline shell com extremo cuidado e comandos fixos.
Qual código de saída significa falha?
Não zero - mapeie códigos conhecidos na documentação do script (por exemplo, erro de uso 2).
Como oculto senhas em argv?
Passe via variável de ambiente ou stdin - argv é visível em ps em muitos sistemas.
Diferenças no Windows?
Arquivos .cmd podem precisar de shell=True apenas no Windows - isole os branches de plataforma.
Como testo subprocesso?
Faça mock de subprocess.run ou use binários de eco conhecidos em pytest com tmp_path.
Mesclar stdout/stderr?
stderr=subprocess.STDOUT quando um único stream simplifica a análise.
Como isso se relaciona com SSH?
subprocess local em um host remoto via sessão SSH - veja a página SSH para padrões de execução remota.
Relacionado
- Noções Básicas de Scripting - pontos de entrada CLI
- SSH e Execução Remota - comandos remotos
- Scripts Robustos - tratamento de erros
- Análise de Logs e Texto - analisar saída de comando
- Melhores Práticas de Scripting de Sistema - execução segura
Versões do Stack: Esta página foi escrita para Python 3.14.0 (estável 3.14, manutenção 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+ e uv 0.6+.