Python subprocess: Execute comandos externos a partir do Python (Guia completo)
Updated on
Scripts Python frequentemente precisam chamar programas externos. Você pode precisar executar um comando de shell para compactar arquivos, invocar git para controle de versão, chamar um utilitário do sistema como ffmpeg para processar vídeo ou executar um binário compilado como parte de um pipeline de dados. Mas recorrer a os.system() ou a “gambiarras” no estilo de backticks leva a um código frágil, inseguro e impossível de depurar quando algo dá errado.
A dor piora rápido. A saída desaparece no nada porque você não tem como capturá-la. Erros passam silenciosamente porque o código de retorno é ignorado. Um único nome de arquivo fornecido pelo usuário com um espaço ou ponto e vírgula pode transformar seu script inocente em uma vulnerabilidade de injeção de shell. E quando um subprocess trava, todo o seu programa Python trava junto — sem timeout, sem recuperação, sem explicação.
O módulo subprocess do Python é a solução padrão. Ele substitui funções mais antigas como os.system(), os.popen() e o módulo commands (descontinuado) por uma API única e consistente para criar processos, capturar a saída, tratar erros, definir timeouts e construir pipelines. Este guia cobre tudo o que você precisa para usá-lo de forma eficaz e segura.
Início rápido com subprocess.run()
A função subprocess.run(), introduzida no Python 3.5, é a forma recomendada de executar comandos externos. Ela executa um comando, espera ele terminar e retorna um objeto CompletedProcess.
import subprocess
# Run a simple command
result = subprocess.run(["ls", "-la"], capture_output=True, text=True)
print(result.stdout) # standard output as a string
print(result.stderr) # standard error as a string
print(result.returncode) # 0 means successParâmetros principais:
capture_output=Truecaptura stdout e stderr (equivalente astdout=subprocess.PIPE, stderr=subprocess.PIPE)text=Truedecodifica a saída como strings em vez de bytes- O comando é passado como uma lista de strings, onde cada argumento é um elemento separado
import subprocess
# Run a command with arguments
result = subprocess.run(
["python", "--version"],
capture_output=True,
text=True
)
print(result.stdout.strip()) # e.g., "Python 3.12.1"Entendendo a lista de comando vs string
O primeiro argumento de subprocess.run() pode ser uma lista ou uma string. Essa distinção importa para correção e segurança.
Forma em lista (recomendada)
Cada elemento da lista é um argumento separado. O Python os passa diretamente ao sistema operacional sem interpretação do shell.
import subprocess
# Each argument is a separate list element
result = subprocess.run(
["grep", "-r", "TODO", "/home/user/project"],
capture_output=True,
text=True
)
print(result.stdout)Nomes de arquivos com espaços, aspas ou caracteres especiais funcionam corretamente porque cada argumento é passado “como está”:
import subprocess
# Filename with spaces -- works correctly as a list element
result = subprocess.run(
["cat", "my file with spaces.txt"],
capture_output=True,
text=True
)Forma em string (exige shell=True)
Passar uma única string exige shell=True, que invoca o shell do sistema (/bin/sh no Unix, cmd.exe no Windows) para interpretar o comando.
import subprocess
# String form requires shell=True
result = subprocess.run(
"ls -la | grep '.py'",
shell=True,
capture_output=True,
text=True
)
print(result.stdout)Isso habilita recursos do shell como pipes (|), redirecionamentos (>), globbing (*.py) e expansão de variáveis de ambiente ($HOME). Mas também introduz riscos sérios de segurança, que cobrimos na seção de Segurança abaixo.
Capturando saída
Capturar stdout e stderr separadamente
import subprocess
result = subprocess.run(
["python", "-c", "import sys; print('out'); print('err', file=sys.stderr)"],
capture_output=True,
text=True
)
print(f"stdout: {result.stdout}") # "out\n"
print(f"stderr: {result.stderr}") # "err\n"Mesclar stderr em stdout
Às vezes você quer toda a saída em um único fluxo. Use stderr=subprocess.STDOUT:
import subprocess
result = subprocess.run(
["python", "-c", "import sys; print('out'); print('err', file=sys.stderr)"],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True
)
print(result.stdout) # Contains both "out\n" and "err\n"Descartar saída
Envie a saída para subprocess.DEVNULL para suprimí-la:
import subprocess
# Run silently -- discard all output
result = subprocess.run(
["apt-get", "update"],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL
)Saída binária
Omita text=True para receber bytes brutos. Útil para dados binários como imagens ou arquivos compactados:
import subprocess
# Capture binary output (e.g., from curl)
result = subprocess.run(
["curl", "-s", "https://example.com/image.png"],
capture_output=True
)
image_bytes = result.stdout # bytes object
print(f"Downloaded {len(image_bytes)} bytes")Tratamento de erros
Verificar códigos de retorno manualmente
Por padrão, subprocess.run() não lança uma exceção quando um comando falha. Você precisa verificar returncode por conta própria:
import subprocess
result = subprocess.run(
["ls", "/nonexistent/path"],
capture_output=True,
text=True
)
if result.returncode != 0:
print(f"Command failed with code {result.returncode}")
print(f"Error: {result.stderr}")Exceção automática em falha com check=True
O parâmetro check=True lança subprocess.CalledProcessError quando o código de retorno é diferente de zero:
import subprocess
try:
result = subprocess.run(
["ls", "/nonexistent/path"],
capture_output=True,
text=True,
check=True
)
except subprocess.CalledProcessError as e:
print(f"Command failed with return code {e.returncode}")
print(f"stderr: {e.stderr}")
print(f"stdout: {e.stdout}")Esse é o padrão recomendado para comandos que sempre deveriam ter sucesso. Ele força você a lidar com falhas explicitamente, em vez de ignorá-las silenciosamente.
Lidar com comando não encontrado
Se o executável não existir, o Python lança FileNotFoundError:
import subprocess
try:
result = subprocess.run(
["nonexistent_command"],
capture_output=True,
text=True
)
except FileNotFoundError:
print("Command not found -- is it installed and in PATH?")Timeouts
Processos demorados ou travados podem bloquear seu script para sempre. O parâmetro timeout (em segundos) encerra o processo e lança subprocess.TimeoutExpired se ele não terminar a tempo:
import subprocess
try:
result = subprocess.run(
["sleep", "30"],
timeout=5,
capture_output=True,
text=True
)
except subprocess.TimeoutExpired:
print("Process timed out after 5 seconds")Isso é essencial para comandos de rede, APIs externas ou qualquer processo que possa travar:
import subprocess
def run_with_timeout(cmd, timeout_seconds=30):
"""Run a command with timeout and error handling."""
try:
result = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=timeout_seconds,
check=True
)
return result.stdout
except subprocess.TimeoutExpired:
print(f"Command timed out after {timeout_seconds}s: {' '.join(cmd)}")
return None
except subprocess.CalledProcessError as e:
print(f"Command failed (code {e.returncode}): {e.stderr}")
return None
except FileNotFoundError:
print(f"Command not found: {cmd[0]}")
return None
# Usage
output = run_with_timeout(["ping", "-c", "4", "example.com"], timeout_seconds=10)
if output:
print(output)Passando entrada para um processo
Use o parâmetro input para enviar dados para stdin de um processo:
import subprocess
# Send text to stdin
result = subprocess.run(
["grep", "error"],
input="line 1\nerror on line 2\nline 3\nerror on line 4\n",
capture_output=True,
text=True
)
print(result.stdout)
# "error on line 2\nerror on line 4\n"Isso substitui o padrão comum de fazer pipe de dados via comandos do shell:
import subprocess
import json
# Send JSON to a processing command
data = {"name": "Alice", "score": 95}
json_string = json.dumps(data)
result = subprocess.run(
["python", "-c", "import sys, json; d = json.load(sys.stdin); print(d['name'])"],
input=json_string,
capture_output=True,
text=True
)
print(result.stdout.strip()) # "Alice"Variáveis de ambiente
Por padrão, subprocessos herdam o ambiente atual. Você pode modificá-lo:
import subprocess
import os
# Add or override environment variables
custom_env = os.environ.copy()
custom_env["API_KEY"] = "secret123"
custom_env["DEBUG"] = "true"
result = subprocess.run(
["python", "-c", "import os; print(os.environ.get('API_KEY'))"],
env=custom_env,
capture_output=True,
text=True
)
print(result.stdout.strip()) # "secret123"Sempre use os.environ.copy() como base. Passar um dict sem o ambiente existente remove todas as variáveis herdadas, o que pode quebrar comandos que dependem de PATH, HOME ou outras variáveis do sistema.
Diretório de trabalho
O parâmetro cwd define o diretório de trabalho do subprocesso:
import subprocess
# Run git status in a specific repository
result = subprocess.run(
["git", "status", "--short"],
cwd="/home/user/my-project",
capture_output=True,
text=True
)
print(result.stdout)subprocess.run() vs Popen: quando usar cada um
subprocess.run() é um wrapper de conveniência em torno de subprocess.Popen. Para a maioria dos casos, run() é suficiente. Use Popen quando você precisa de:
- Streaming de saída em tempo real (linha por linha conforme é produzido)
- Interagir com um processo em execução (enviar input, ler output em loop)
- Construir pipelines em múltiplas etapas conectando vários processos
- Execução não bloqueante com controle manual do ciclo de vida do processo
Tabela de comparação
| Recurso | subprocess.run() | subprocess.Popen | os.system() |
|---|---|---|---|
| Recomendado | Sim (Python 3.5+) | Sim (avançado) | Não (padrão legado) |
| Capturar saída | Sim (capture_output=True) | Sim (via PIPE) | Não |
| Valor de retorno | Objeto CompletedProcess | Objeto de processo Popen | Código de saída (int) |
| Suporte a timeout | Sim (timeout param) | Manual (via wait/communicate) | Não |
| Verificação de erro | check=True lança exceção | Manual | Precisa interpretar o exit code |
| Entrada para stdin | Parâmetro input | communicate() ou stdin.write() | Não |
| Saída em tempo real | Não (espera concluir) | Sim (stream linha a linha) | Saída vai para o terminal |
| Pipelines | Limitado (comando único) | Sim (encadear múltiplos Popen) | Sim (via string de shell) |
| Segurança | Seguro com args em lista | Seguro com args em lista | Risco de injeção de shell |
| Recursos do shell | Só com shell=True | Só com shell=True | Sempre usa shell |
Avançado: subprocess.Popen
Popen dá controle total sobre o ciclo de vida do processo. O construtor inicia o processo imediatamente e retorna um objeto Popen com o qual você pode interagir.
Uso básico do Popen
import subprocess
proc = subprocess.Popen(
["ls", "-la"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True
)
stdout, stderr = proc.communicate() # Wait for completion and get output
print(f"Return code: {proc.returncode}")
print(stdout)Streaming de saída em tempo real
Diferente do run(), o Popen permite ler a saída linha por linha conforme ela é produzida:
import subprocess
proc = subprocess.Popen(
["ping", "-c", "5", "example.com"],
stdout=subprocess.PIPE,
text=True
)
# Read output line by line as it arrives
for line in proc.stdout:
print(f"[LIVE] {line.strip()}")
proc.wait() # Wait for process to finish
print(f"Exit code: {proc.returncode}")Isso é essencial para comandos longos em que você quer mostrar progresso ou registrar logs em tempo real:
import subprocess
import sys
def run_with_live_output(cmd):
"""Run a command and stream its output in real time."""
proc = subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
bufsize=1 # Line-buffered
)
output_lines = []
for line in proc.stdout:
line = line.rstrip()
print(line)
output_lines.append(line)
proc.wait()
return proc.returncode, "\n".join(output_lines)
# Usage
code, output = run_with_live_output(["pip", "install", "requests"])
print(f"\nFinished with exit code: {code}")Construindo pipelines
Conecte múltiplos comandos fazendo pipe do stdout de um processo para o stdin de outro:
import subprocess
# Equivalent to: cat /var/log/syslog | grep "error" | wc -l
p1 = subprocess.Popen(
["cat", "/var/log/syslog"],
stdout=subprocess.PIPE
)
p2 = subprocess.Popen(
["grep", "error"],
stdin=p1.stdout,
stdout=subprocess.PIPE
)
# Allow p1 to receive SIGPIPE if p2 exits early
p1.stdout.close()
p3 = subprocess.Popen(
["wc", "-l"],
stdin=p2.stdout,
stdout=subprocess.PIPE,
text=True
)
p2.stdout.close()
output, _ = p3.communicate()
print(f"Error count: {output.strip()}")A chamada p1.stdout.close() após conectá-lo ao p2 é importante. Ela permite que p1 receba um sinal SIGPIPE se p2 sair mais cedo, prevenindo deadlocks.
Comunicação interativa com processo
import subprocess
# Start a Python REPL as a subprocess
proc = subprocess.Popen(
["python", "-i"],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True
)
# Send commands and get results
stdout, stderr = proc.communicate(input="print(2 + 2)\nprint('hello')\n")
print(f"stdout: {stdout}")
print(f"stderr: {stderr}")shell=True: poder e perigo
Definir shell=True executa o comando através do shell do sistema, habilitando recursos do shell, mas introduzindo riscos de segurança.
Quando shell=True é útil
import subprocess
# Shell features: pipes, redirects, globbing, env vars
result = subprocess.run(
"ls *.py | wc -l",
shell=True,
capture_output=True,
text=True
)
print(f"Python files: {result.stdout.strip()}")
# Environment variable expansion
result = subprocess.run(
"echo $HOME",
shell=True,
capture_output=True,
text=True
)
print(result.stdout.strip())O problema de injeção de shell
Nunca passe input do usuário sem sanitização para shell=True:
import subprocess
# DANGEROUS -- shell injection vulnerability
user_input = "file.txt; rm -rf /" # malicious input
subprocess.run(f"cat {user_input}", shell=True) # Executes "rm -rf /"!
# SAFE -- use list form without shell=True
subprocess.run(["cat", user_input]) # Treats entire string as filenameSe você realmente precisar usar shell=True com valores dinâmicos, use shlex.quote():
import subprocess
import shlex
user_input = "file with spaces.txt; rm -rf /"
safe_input = shlex.quote(user_input)
# shlex.quote wraps in single quotes, neutralizing shell metacharacters
result = subprocess.run(
f"cat {safe_input}",
shell=True,
capture_output=True,
text=True
)Mas a abordagem mais segura é evitar shell=True por completo e reproduzir os recursos do shell em Python:
import subprocess
import glob
# Instead of: subprocess.run("ls *.py | wc -l", shell=True)
py_files = glob.glob("*.py")
print(f"Python files: {len(py_files)}")
# Instead of: subprocess.run("cat file1.txt file2.txt > combined.txt", shell=True)
with open("combined.txt", "w") as outfile:
result = subprocess.run(
["cat", "file1.txt", "file2.txt"],
stdout=outfile
)Boas práticas de segurança
| Prática | Faça | Não faça |
|---|---|---|
| Formato de comando | ["cmd", "arg1", "arg2"] | f"cmd {user_input}" com shell=True |
| Input do usuário | shlex.quote() se o shell for necessário | Concatenar strings em comandos |
| Modo shell | shell=False (padrão) | shell=True com input não confiável |
| Caminho do executável | Caminhos completos como /usr/bin/git | Confiar no PATH em código crítico de segurança |
| Validação de input | Validar e sanitizar antes de passar | Passar input bruto do usuário para comandos |
import subprocess
import shlex
from pathlib import Path
def safe_file_operation(filename):
"""Safely run a command with user-supplied filename."""
# Validate input
path = Path(filename)
if not path.exists():
raise FileNotFoundError(f"File not found: {filename}")
# Check for path traversal
resolved = path.resolve()
allowed_dir = Path("/home/user/uploads").resolve()
if not str(resolved).startswith(str(allowed_dir)):
raise PermissionError("Access denied: file outside allowed directory")
# Use list form -- no shell injection possible
result = subprocess.run(
["wc", "-l", str(resolved)],
capture_output=True,
text=True,
check=True
)
return result.stdout.strip()Exemplos do mundo real
Executar comandos do git
import subprocess
def git_status(repo_path):
"""Get git status for a repository."""
result = subprocess.run(
["git", "status", "--porcelain"],
cwd=repo_path,
capture_output=True,
text=True,
check=True
)
return result.stdout.strip()
def git_log(repo_path, n=5):
"""Get last n commit messages."""
result = subprocess.run(
["git", "log", f"--oneline", f"-{n}"],
cwd=repo_path,
capture_output=True,
text=True,
check=True
)
return result.stdout.strip()
status = git_status("/home/user/my-project")
if status:
print("Uncommitted changes:")
print(status)
else:
print("Working directory clean")Compactar e extrair arquivos
import subprocess
def compress_directory(source_dir, output_file):
"""Create a tar.gz archive of a directory."""
subprocess.run(
["tar", "-czf", output_file, "-C", source_dir, "."],
check=True
)
print(f"Created archive: {output_file}")
def extract_archive(archive_file, dest_dir):
"""Extract a tar.gz archive."""
subprocess.run(
["tar", "-xzf", archive_file, "-C", dest_dir],
check=True
)
print(f"Extracted to: {dest_dir}")
compress_directory("/home/user/data", "/tmp/data_backup.tar.gz")Verificar informações do sistema
import subprocess
def get_disk_usage():
"""Get disk usage summary."""
result = subprocess.run(
["df", "-h", "/"],
capture_output=True,
text=True,
check=True
)
return result.stdout
def get_memory_info():
"""Get memory usage on Linux."""
result = subprocess.run(
["free", "-h"],
capture_output=True,
text=True,
check=True
)
return result.stdout
def get_process_list(filter_name=None):
"""List running processes, optionally filtered."""
cmd = ["ps", "aux"]
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
if filter_name:
lines = result.stdout.strip().split("\n")
header = lines[0]
matching = [line for line in lines[1:] if filter_name in line]
return header + "\n" + "\n".join(matching)
return result.stdout
print(get_disk_usage())Processar arquivos de dados com ferramentas externas
import subprocess
import csv
import io
def sort_csv_by_column(input_file, column_index=1):
"""Sort a CSV file using the system sort command (fast for large files)."""
result = subprocess.run(
["sort", "-t,", f"-k{column_index}", input_file],
capture_output=True,
text=True,
check=True
)
return result.stdout
def count_lines(filepath):
"""Count lines in a file using wc (faster than Python for huge files)."""
result = subprocess.run(
["wc", "-l", filepath],
capture_output=True,
text=True,
check=True
)
return int(result.stdout.strip().split()[0])
def search_in_files(directory, pattern, file_type="*.py"):
"""Search for a pattern in files using grep."""
result = subprocess.run(
["grep", "-rn", "--include", file_type, pattern, directory],
capture_output=True,
text=True
)
# grep returns exit code 1 if no matches found (not an error)
if result.returncode == 0:
return result.stdout
elif result.returncode == 1:
return "" # No matches
else:
raise subprocess.CalledProcessError(result.returncode, result.args)
matches = search_in_files("/home/user/project", "TODO")
if matches:
print(matches)
else:
print("No TODOs found")Script de deploy automatizado
import subprocess
import sys
def deploy(repo_path, branch="main"):
"""Simple deployment script using subprocess."""
steps = [
(["git", "fetch", "origin"], "Fetching latest changes"),
(["git", "checkout", branch], f"Switching to {branch}"),
(["git", "pull", "origin", branch], "Pulling latest code"),
(["pip", "install", "-r", "requirements.txt"], "Installing dependencies"),
(["python", "manage.py", "migrate"], "Running migrations"),
(["python", "manage.py", "collectstatic", "--noinput"], "Collecting static files"),
]
for cmd, description in steps:
print(f"\n--- {description} ---")
try:
result = subprocess.run(
cmd,
cwd=repo_path,
capture_output=True,
text=True,
check=True,
timeout=120
)
if result.stdout:
print(result.stdout)
except subprocess.CalledProcessError as e:
print(f"FAILED: {e.stderr}")
sys.exit(1)
except subprocess.TimeoutExpired:
print(f"TIMEOUT: {description} took too long")
sys.exit(1)
print("\nDeployment complete")Considerações cross-platform
Comandos se comportam de forma diferente no Windows vs Unix. Escreva código portátil:
import subprocess
import platform
def run_command(cmd_unix, cmd_windows=None):
"""Run a command with platform awareness."""
if platform.system() == "Windows":
cmd = cmd_windows or cmd_unix
# Windows often needs shell=True for built-in commands
return subprocess.run(cmd, shell=True, capture_output=True, text=True)
else:
return subprocess.run(cmd, capture_output=True, text=True)
# List directory contents
result = run_command(
cmd_unix=["ls", "-la"],
cmd_windows="dir"
)
print(result.stdout)Principais diferenças por plataforma:
| Recurso | Unix/macOS | Windows |
|---|---|---|
| Shell | /bin/sh | cmd.exe |
| Separador de caminho | / | \\ |
| Comandos built-in (dir, copy) | Não disponíveis | Exigem shell=True |
| Extensão de executável | Não necessária | Às vezes .exe é necessária |
| Manipulação de sinais | Sinais POSIX completos | Limitado |
shlex.quote() | Funciona | Use subprocess.list2cmdline() |
Executando subprocess em Jupyter Notebooks
Executar comandos de shell a partir de notebooks Jupyter é um fluxo comum para cientistas de dados. Embora o Jupyter suporte a sintaxe !command para chamadas rápidas ao shell, subprocess te dá tratamento de erros adequado e captura de saída dentro do seu código Python.
Ao depurar chamadas de subprocess em notebooks — especialmente quando comandos falham silenciosamente ou produzem saída inesperada — RunCell (opens in a new tab) pode ajudar. O RunCell é um agente de IA para Jupyter que entende o contexto do seu notebook. Ele pode diagnosticar por que um comando de subprocess falha, sugerir os argumentos corretos e lidar com particularidades específicas de plataforma. Em vez de alternar entre terminal e notebook para depurar um comando de shell, o RunCell rastreia o problema diretamente na sua célula.
import subprocess
# In a Jupyter notebook: capture and display command output
result = subprocess.run(
["pip", "list", "--format=columns"],
capture_output=True,
text=True
)
# Display as formatted output in the notebook
print(result.stdout)Erros comuns e como corrigir
Erro 1: Esquecer de capturar a saída
import subprocess
# Output goes to terminal, not captured
result = subprocess.run(["ls", "-la"])
print(result.stdout) # None!
# Fix: add capture_output=True
result = subprocess.run(["ls", "-la"], capture_output=True, text=True)
print(result.stdout) # Actual outputErro 2: Usar uma string sem shell=True
import subprocess
# Fails: string passed without shell=True
# subprocess.run("ls -la") # FileNotFoundError: "ls -la" is not a program
# Fix option 1: use a list
subprocess.run(["ls", "-la"])
# Fix option 2: use shell=True (less safe)
subprocess.run("ls -la", shell=True)Erro 3: Ignorar erros
import subprocess
# Bad: silently continues on failure
result = subprocess.run(["rm", "/important/file"], capture_output=True, text=True)
# ... continues even if rm failed
# Good: check=True raises exception on failure
try:
result = subprocess.run(
["rm", "/important/file"],
capture_output=True,
text=True,
check=True
)
except subprocess.CalledProcessError as e:
print(f"Failed to delete: {e.stderr}")Erro 4: Deadlock com Popen
import subprocess
# DEADLOCK: stdout buffer fills up, process blocks, .wait() waits forever
proc = subprocess.Popen(["command_with_lots_of_output"], stdout=subprocess.PIPE)
proc.wait() # Deadlock!
# Fix: use communicate() which handles buffering
proc = subprocess.Popen(["command_with_lots_of_output"], stdout=subprocess.PIPE, text=True)
stdout, stderr = proc.communicate() # SafeErro 5: Não lidar com encoding
import subprocess
# Bytes output can cause issues
result = subprocess.run(["cat", "data.txt"], capture_output=True)
# result.stdout is bytes, not str
# Fix: use text=True or encoding parameter
result = subprocess.run(["cat", "data.txt"], capture_output=True, text=True)
# For specific encodings:
result = subprocess.run(
["cat", "data.txt"],
capture_output=True,
encoding="utf-8",
errors="replace" # Handle invalid bytes
)Referência completa de parâmetros do subprocess.run()
import subprocess
result = subprocess.run(
args, # Command as list or string
stdin=None, # Input source (PIPE, DEVNULL, file object, or None)
stdout=None, # Output destination
stderr=None, # Error destination
capture_output=False, # Shorthand for stdout=PIPE, stderr=PIPE
text=False, # Decode output as strings (alias: universal_newlines)
shell=False, # Run through system shell
cwd=None, # Working directory
timeout=None, # Seconds before TimeoutExpired
check=False, # Raise CalledProcessError on non-zero exit
env=None, # Environment variables dict
encoding=None, # Output encoding (alternative to text=True)
errors=None, # Encoding error handling ('strict', 'replace', 'ignore')
input=None, # String/bytes to send to stdin
)FAQ
O que é o módulo subprocess no Python?
O módulo subprocess é a biblioteca padrão do Python para executar comandos e programas externos a partir de scripts Python. Ele substituiu abordagens antigas como os.system(), os.popen() e o módulo commands. Ele fornece funções para criar novos processos, conectar aos pipes de entrada/saída/erro, obter códigos de retorno e lidar com timeouts. A interface principal é subprocess.run() para execução simples de comandos, e subprocess.Popen para casos avançados que exigem E/S em tempo real ou pipelines de processos.
Qual é a diferença entre subprocess.run() e subprocess.Popen?
subprocess.run() é uma função de alto nível e de conveniência que executa um comando, espera ele terminar e retorna um objeto CompletedProcess com a saída. É a escolha certa para a maioria das tarefas. subprocess.Popen é uma classe de nível mais baixo que te dá controle direto sobre o processo: você pode fazer streaming da saída linha por linha, enviar input de forma interativa, construir pipelines com múltiplos processos e gerenciar manualmente o ciclo de vida do processo. Use Popen quando precisar de streaming de saída em tempo real ou ao conectar vários processos.
shell=True é perigoso no subprocess?
Sim, usar shell=True com input não confiável cria uma vulnerabilidade de injeção de shell. Quando shell=True está definido, a string do comando é passada ao shell do sistema para interpretação, o que significa que metacaracteres do shell como ;, |, && e $() são executados. Um atacante poderia injetar comandos arbitrários. O padrão seguro é shell=False com comandos passados como lista. Se você precisar usar shell=True, sanitize o input com shlex.quote() e nunca passe input bruto do usuário.
Como capturo a saída de um comando subprocess?
Use capture_output=True e text=True com subprocess.run(). A saída fica em result.stdout (como string) e os erros em result.stderr. Por exemplo: result = subprocess.run(["ls", "-la"], capture_output=True, text=True) e então acesse result.stdout. Sem text=True, a saída é retornada como bytes.
Como lidar com timeouts de subprocess no Python?
Passe o parâmetro timeout (em segundos) para subprocess.run(). Se o processo exceder o timeout, o Python o encerra e lança subprocess.TimeoutExpired. Exemplo: subprocess.run(["slow_command"], timeout=30). Para Popen, use proc.communicate(timeout=30) ou proc.wait(timeout=30). Sempre envolva código sensível a timeout em um bloco try/except.
Por que os.system() ainda funciona se subprocess é recomendado?
os.system() não é formalmente descontinuado, mas é considerado uma interface legada. Ele executa comandos via shell (como shell=True), não consegue capturar saída, não tem mecanismo de timeout e retorna apenas o status de saída. subprocess.run() faz tudo o que os.system() faz, além de captura de saída, tratamento de erros, timeouts e passagem segura de argumentos. Todo código novo deve usar subprocess.
Conclusão
O módulo subprocess é a ferramenta definitiva do Python para executar comandos externos. Use subprocess.run() para execução direta de comandos — ele lida com captura de saída, verificação de erros, timeouts e envio de input em uma única chamada de função. Recorra a subprocess.Popen apenas quando você precisar de streaming de saída em tempo real, comunicação interativa com o processo ou pipelines em múltiplas etapas.
O hábito mais importante é evitar shell=True com input fornecido pelo usuário. Passe comandos como listas para eliminar completamente riscos de injeção de shell. Use check=True para detectar falhas cedo. Defina timeout para evitar processos travados. E use text=True para trabalhar com strings em vez de bytes brutos.
De automação do git a orquestração de pipelines de dados, subprocess oferece o controle e a segurança que os.system() nunca poderia oferecer. Domine esses padrões e você poderá integrar com confiança qualquer ferramenta externa aos seus fluxos de trabalho em Python.