shlex em Python: argumentos de shell sem quebrar comandos
Aprenda shlex em Python para separar comandos, citar argumentos com segurança, ler arquivos de configuração e evitar erros comuns com shell=True.
O módulo shlex ajuda a trabalhar com linhas de comando e argumentos de shell em Python sem separar texto ingenuamente por espaços nem montar comandos frágeis com concatenação. Ele faz parte da biblioteca padrão e oferece três atalhos importantes: shlex.split() para analisar uma linha, shlex.quote() para representar um único argumento com segurança em shells Unix e shlex.join() para transformar uma lista de argumentos em texto legível.
A recomendação direta é: para executar programas, prefira sempre subprocess.run() com uma lista de argumentos e shell=False. Use shlex.split() quando você recebeu uma linha de comando confiável e precisa convertê-la em lista; shlex.join() para exibir ou registrar uma lista; e shlex.quote() apenas quando um shell Unix for realmente inevitável. shlex reduz erros de parsing, mas não transforma entrada não confiável em um comando seguro por mágica.
Neste guia, você vai aprender a diferença entre texto e argumentos, evitar injeção de comandos, montar uma ferramenta prática de execução controlada e entender os limites no Windows, PowerShell e cmd.exe.
O problema: espaços não separam argumentos de forma confiável
Considere esta linha:
python gerar_relatorio.py --cliente "Loja São José" --saida "relatórios/julho 2026.csv"
Usar str.split() produz o resultado errado:
comando = 'python gerar_relatorio.py --cliente "Loja São José"'
print(comando.split())
A lista resultante mantém pedaços das aspas e divide o nome do cliente:
[
"python",
"gerar_relatorio.py",
"--cliente",
'"Loja',
"São",
'José"',
]
shlex.split() entende aspas e escapes em uma sintaxe semelhante à de shells Unix:
import shlex
comando = 'python gerar_relatorio.py --cliente "Loja São José"'
argumentos = shlex.split(comando)
print(argumentos)
Resultado:
[
"python",
"gerar_relatorio.py",
"--cliente",
"Loja São José",
]
Essa lista é exatamente o formato esperado por subprocess.run():
import shlex
import subprocess
linha = 'python gerar_relatorio.py --cliente "Loja São José"'
argumentos = shlex.split(linha)
resultado = subprocess.run(
argumentos,
check=True,
text=True,
capture_output=True,
timeout=30,
)
print(resultado.stdout)
O shell não participa dessa execução. O sistema recebe o executável e cada argumento separadamente. Espaços dentro de Loja São José não criam novos argumentos, e caracteres como ;, && ou $() não ganham automaticamente significado especial de shell.
Para conhecer check, timeout, captura de saída e tratamento de erros, veja o guia de subprocess em Python.
shlex.split: de linha de comando para lista
A assinatura mais comum é simples:
import shlex
argumentos = shlex.split('backup --origem "/dados da empresa" --compactar')
print(argumentos)
Saída:
["backup", "--origem", "/dados da empresa", "--compactar"]
A função também entende aspas simples:
shlex.split("enviar --mensagem 'Processamento concluído com sucesso'")
E escapes em modo POSIX:
shlex.split(r"processar arquivo\ com\ espacos.csv")
# ['processar', 'arquivo com espacos.csv']
Por padrão, posix=True. Esse comportamento é adequado quando o texto segue convenções de Bash, sh e shells semelhantes. Você pode desativá-lo:
shlex.split('programa "C:\\Meus Arquivos\\entrada.csv"', posix=False)
Porém, posix=False não transforma shlex em um parser completo de cmd.exe ou PowerShell. Ele apenas muda algumas regras de aspas e escapes do lexer. Se sua aplicação recebe comandos específicos do Windows, não suponha que a mesma string terá semântica idêntica em todas as plataformas.
Quando split é apropriado
shlex.split() funciona bem quando:
- um operador confiável informa opções em um arquivo de configuração;
- uma aplicação recebe uma variável interna como
--formato csv --limite 100; - você precisa converter uma instrução textual documentada em argumentos;
- está construindo uma interface administrativa controlada;
- quer testar como uma linha Unix será dividida.
Ele é uma escolha ruim quando o sistema aceita de qualquer usuário uma linha inteira e a executa sem validar o programa e as opções. Nesse caso, o problema não é apenas separar corretamente: é permitir que a pessoa escolha o que será executado.
Não monte comandos com f-string e shell=True
Este padrão é perigoso:
import subprocess
arquivo = input("Arquivo: ")
subprocess.run(f"cat {arquivo}", shell=True, check=True)
Se a entrada for:
relatorio.txt; echo comando-extra
o shell interpreta ; como separador entre comandos. Em uma aplicação real, o segundo trecho poderia fazer algo muito pior.
A correção preferida é remover o shell:
import subprocess
arquivo = input("Arquivo: ")
subprocess.run(
["cat", "--", arquivo],
shell=False,
check=True,
)
Agora o conteúdo de arquivo é entregue como um único argumento. O --, quando suportado pelo programa chamado, marca o fim das opções e impede que um nome iniciado por hífen seja interpretado como flag.
Em código portátil, você poderia evitar cat e ler o arquivo diretamente com Python:
from pathlib import Path
arquivo = Path(input("Arquivo: "))
print(arquivo.read_text(encoding="utf-8"))
Essa abordagem elimina a dependência de um programa externo. O guia de pathlib mostra como validar e manipular caminhos de forma mais clara.
Regra prática: se você não precisa de pipes, redirecionamento, expansão de variáveis ou outra funcionalidade do shell, não use
shell=True.
shlex.quote: protegendo um único argumento Unix
Algumas situações realmente dependem de um shell. Um exemplo é gerar uma instrução que será executada depois via SSH por uma ferramenta legada. Nesses casos, cada valor variável precisa virar uma única palavra de shell.
import shlex
arquivo = "Relatórios/resultado final.csv"
seguro = shlex.quote(arquivo)
print(seguro)
Saída típica:
'Relatórios/resultado final.csv'
Com uma entrada mais problemática:
import shlex
nome = "arquivo; rm -rf exemplo"
print(shlex.quote(nome))
O ponto e vírgula fica dentro da representação citada e deixa de atuar como separador de comandos no shell Unix.
Uma montagem aceitável, quando o shell é requisito explícito, mantém a estrutura fixa:
import shlex
origem = "relatórios/julho 2026.csv"
destino = "/srv/importações/entrada.csv"
comando_remoto = (
"install -- "
f"{shlex.quote(origem)} "
f"{shlex.quote(destino)}"
)
O programa (install) e a estrutura são definidos pelo código. Apenas os argumentos passam por quote().
Não faça isto:
# Continua permitindo que o usuário escolha toda a estrutura do comando.
comando = shlex.quote(input("Comando completo: "))
Citar a linha inteira transforma tudo em uma única palavra; não cria um comando válido e também não resolve o modelo de autorização. quote() serve para um argumento, não para revisar uma linguagem de shell arbitrária.
Limite importante: Unix não é PowerShell
A documentação de shlex.quote() é voltada a shells compatíveis com Unix. Regras de quoting do PowerShell e do cmd.exe são diferentes. Uma string produzida por quote() não deve ser considerada segura ou correta nesses ambientes.
Para automações Windows, prefira ainda mais fortemente passar uma lista a subprocess sem shell. Se precisar chamar PowerShell, defina um script fixo, use parâmetros formais e trate dados como dados — não como trechos concatenados de código PowerShell.
shlex.join: de lista para uma linha legível
A partir do Python 3.8, shlex.join() faz a operação inversa de split() para uma lista de strings:
import shlex
argumentos = [
"python",
"gerar_relatorio.py",
"--cliente",
"Loja São José",
"--saida",
"relatórios/julho 2026.csv",
]
print(shlex.join(argumentos))
Saída:
python gerar_relatorio.py --cliente 'Loja São José' --saida 'relatórios/julho 2026.csv'
Isso é útil para:
- mostrar ao operador como reproduzir uma execução;
- incluir o comando em uma mensagem de erro;
- criar um modo
--dry-run; - registrar a intenção de uma automação;
- gerar documentação e testes de snapshot.
Mas existe uma regra adicional: não registre segredos. Se a lista contém --token, senha, cookie, CPF, payload pessoal ou URL assinada, produza uma versão sanitizada antes do log.
import shlex
def ocultar_segredos(argumentos: list[str]) -> list[str]:
resultado = argumentos.copy()
opcoes_sensiveis = {"--token", "--password", "--api-key"}
for indice, valor in enumerate(resultado[:-1]):
if valor in opcoes_sensiveis:
resultado[indice + 1] = "[OCULTO]"
return resultado
argumentos = ["cliente-api", "--token", "segredo-real", "listar"]
print(shlex.join(ocultar_segredos(argumentos)))
Resultado:
cliente-api --token '[OCULTO]' listar
O artigo sobre logging em Python aprofunda níveis, contexto e logs estruturados. Em automações, um comando legível ajuda no diagnóstico, mas nunca deve transformar o log em repositório de credenciais.
Exemplo prático: executor controlado de tarefas
Imagine uma equipe que oferece três tarefas internas: gerar relatório, validar arquivos e importar dados. Em vez de aceitar qualquer comando, o programa mantém uma lista explícita de executáveis permitidos e recebe apenas opções adicionais.
from __future__ import annotations
import shlex
import subprocess
from dataclasses import dataclass
@dataclass(frozen=True)
class Tarefa:
comando_base: tuple[str, ...]
timeout: int
TAREFAS = {
"relatorio": Tarefa(
comando_base=("python", "scripts/gerar_relatorio.py"),
timeout=120,
),
"validar": Tarefa(
comando_base=("python", "scripts/validar_arquivos.py"),
timeout=60,
),
"importar": Tarefa(
comando_base=("python", "scripts/importar_dados.py"),
timeout=300,
),
}
def executar_tarefa(nome: str, opcoes_texto: str) -> subprocess.CompletedProcess[str]:
try:
tarefa = TAREFAS[nome]
except KeyError as erro:
permitidas = ", ".join(sorted(TAREFAS))
raise ValueError(f"Tarefa inválida. Use: {permitidas}") from erro
opcoes = shlex.split(opcoes_texto, posix=True)
if len(opcoes) > 20:
raise ValueError("Quantidade excessiva de argumentos")
argumentos = [*tarefa.comando_base, *opcoes]
print(f"Executando: {shlex.join(argumentos)}")
return subprocess.run(
argumentos,
shell=False,
check=True,
text=True,
capture_output=True,
timeout=tarefa.timeout,
)
Uso:
resultado = executar_tarefa(
"relatorio",
'--cliente "Loja São José" --mes 2026-07',
)
print(resultado.stdout)
Esse desenho é melhor do que aceitar um comando completo porque:
- o usuário escolhe uma tarefa conhecida, não um executável arbitrário;
shlex.split()preserva argumentos com espaços;subprocessrecebe uma lista e não invoca shell;- cada tarefa tem timeout;
check=Truetransforma código de saída diferente de zero em erro;- o comando exibido vem de
shlex.join(); - existe um limite simples para o número de argumentos.
Ainda falta validar as opções permitidas por tarefa. Para uma CLI maior, use argparse, Typer ou Click dentro dos scripts chamados. A combinação fica assim: shlex separa uma configuração textual confiável, subprocess inicia o processo e o parser da CLI valida tipos e regras de negócio.
Lendo um arquivo simples com a classe shlex
Além das funções auxiliares, o módulo expõe a classe shlex.shlex, que funciona como lexer. Ela pode analisar um formato simples de configuração com palavras, aspas e comentários.
Arquivo tarefas.conf:
# nome formato pasta
vendas csv "dados/vendas de julho"
estoque json dados/estoque
Leitura:
from pathlib import Path
import shlex
def ler_tokens(caminho: Path) -> list[str]:
with caminho.open(encoding="utf-8") as arquivo:
lexer = shlex.shlex(arquivo, infile=str(caminho), posix=True)
lexer.whitespace_split = True
lexer.commenters = "#"
return list(lexer)
print(ler_tokens(Path("tarefas.conf")))
Os comentários são ignorados e o caminho com espaços permanece como um token. Para processar por linha e preservar registros:
from pathlib import Path
import shlex
def ler_tarefas(caminho: Path) -> list[tuple[str, str, str]]:
tarefas = []
for numero, linha in enumerate(
caminho.read_text(encoding="utf-8").splitlines(),
start=1,
):
linha = linha.strip()
if not linha or linha.startswith("#"):
continue
tokens = shlex.split(linha, comments=True, posix=True)
if len(tokens) != 3:
raise ValueError(f"Linha {numero}: esperados 3 campos")
nome, formato, pasta = tokens
tarefas.append((nome, formato, pasta))
return tarefas
Para configuração de aplicações novas, formatos como TOML, JSON ou YAML costumam ser mais expressivos. Use shlex quando o formato é deliberadamente pequeno e orientado a tokens. Não recrie uma linguagem complexa de configuração com dezenas de regras se um parser padronizado já resolve.
punctuation_chars e operadores de shell
A classe shlex pode tratar caracteres como |, &, ;, < e > como pontuação. Isso é útil para análise e ferramentas educacionais:
import shlex
lexer = shlex.shlex(
"gerar | comprimir && publicar",
posix=True,
punctuation_chars=True,
)
lexer.whitespace_split = True
print(list(lexer))
Resultado aproximado:
["gerar", "|", "comprimir", "&&", "publicar"]
Não confunda tokenização com execução segura. Reconhecer && não significa que seu programa implementa todas as regras, precedências, expansões e redirecionamentos de Bash. Se você precisa executar um pipeline fixo, conecte processos explicitamente com subprocess.Popen ou faça as etapas em Python. Se precisa interpretar uma linguagem de shell completa, o escopo de segurança é muito maior do que um lexer.
Erros comuns com shlex
1. Usar split em entrada arbitrária e achar que houve validação
argumentos = shlex.split(texto_do_usuario)
subprocess.run(argumentos)
Sem shell=True, metacaracteres perdem boa parte do poder especial, mas a pessoa ainda pode escolher outro programa ou flags perigosas. Use allowlist de executáveis, valide opções e limite recursos.
2. Aplicar quote na linha inteira
shlex.quote("programa --arquivo entrada.csv")
Isso produz uma única palavra citada, não três argumentos. Aplique quote() a valores individuais ou mantenha tudo em lista.
3. Usar quote e depois passar lista ao subprocess
arquivo = shlex.quote("meu arquivo.csv")
subprocess.run(["processar", arquivo])
Com lista e shell=False, não cite manualmente. O programa receberia as aspas como parte literal do argumento em alguns casos. Faça:
subprocess.run(["processar", "meu arquivo.csv"], check=True)
4. Esperar portabilidade perfeita entre shells
Bash, sh, PowerShell e cmd.exe têm regras diferentes. shlex modela principalmente sintaxe Unix. Prefira listas para manter a execução portátil.
5. Registrar o comando com credenciais
shlex.join() melhora a legibilidade, mas não remove segredos. Sanitize a lista antes de qualquer log.
6. Ignorar encoding e acentos
Ao ler arquivos de configuração brasileiros, abra explicitamente com encoding="utf-8". Nomes como São José e produção devem sobreviver ao parsing sem depender da configuração regional da máquina.
7. Permitir argumentos ou saída sem limites
Segurança de processo também inclui timeout, limite de entrada, diretório de trabalho controlado e cuidado com capture_output em comandos que podem produzir gigabytes. Quoting não resolve consumo de recursos.
Testando parsing e execução
Testes de shlex devem cobrir espaços, aspas, acentos e entradas malformadas:
import shlex
import pytest
def test_separa_argumento_com_espacos() -> None:
linha = '--cliente "Loja São José" --mes 2026-07'
assert shlex.split(linha) == [
"--cliente",
"Loja São José",
"--mes",
"2026-07",
]
def test_rejeita_aspas_nao_fechadas() -> None:
with pytest.raises(ValueError):
shlex.split('--cliente "Loja São José')
Para testar o executor, substitua subprocess.run com monkeypatch e confirme a lista recebida, sem iniciar um processo real:
import subprocess
def test_executor_envia_lista_ao_subprocess(monkeypatch) -> None:
recebido = {}
def run_falso(argumentos, **opcoes):
recebido["argumentos"] = argumentos
recebido["opcoes"] = opcoes
return subprocess.CompletedProcess(argumentos, 0, "ok", "")
monkeypatch.setattr(subprocess, "run", run_falso)
executar_tarefa("validar", '--pasta "dados de teste"')
assert recebido["argumentos"] == [
"python",
"scripts/validar_arquivos.py",
"--pasta",
"dados de teste",
]
assert recebido["opcoes"]["shell"] is False
assert recebido["opcoes"]["timeout"] == 60
Veja testes unitários com pytest para fixtures, parametrização e isolamento de dependências externas.
Checklist: qual abordagem escolher?
| Situação | Abordagem recomendada |
|---|---|
| Executar programa com valores variáveis | subprocess.run([programa, arg1, arg2]) |
| Converter uma linha Unix confiável em lista | shlex.split(linha) |
| Mostrar uma lista como comando legível | shlex.join(argumentos) |
| Citar um único argumento para shell Unix | shlex.quote(valor) |
| Validar opções de uma CLI | argparse, Typer ou Click |
| Ler caminhos e arquivos diretamente | pathlib |
| Executar pipeline fixo | processos conectados explicitamente, sem string arbitrária |
| Interpretar PowerShell ou cmd.exe | parâmetros/listas e ferramentas específicas do ambiente |
A melhor solução costuma evitar a string de shell desde o começo. Guarde argumentos como lista na configuração interna, trate caminhos como Path e converta para texto apenas na fronteira que exige texto.
Perguntas frequentes
Para que serve o módulo shlex em Python?
shlex analisa texto com sintaxe parecida com shells Unix. Ele transforma uma linha em lista com split(), cita um argumento com quote() e recompõe uma lista para exibição com join(). A classe shlex também permite ler pequenos formatos orientados a tokens e comentários.
shlex.split torna seguro usar shell=True?
Não. A função resolve parsing, não autorização nem todos os riscos do shell. Prefira subprocess com lista e shell=False. Se o shell for indispensável, deixe a estrutura fixa, cite cada valor variável e não aceite uma linguagem arbitrária de pessoas não confiáveis.
Qual a diferença entre shlex.quote e colocar aspas manualmente?
quote() conhece casos em que o valor já contém aspas ou metacaracteres. Concatenar ' antes e depois falha com entradas mais complexas. Mesmo assim, a função é direcionada a shells Unix e não deve ser reutilizada cegamente em PowerShell ou cmd.exe.
Quando devo usar shlex.join?
Use join() em logs sanitizados, modo dry-run, documentação e mensagens de reprodução. Ele é melhor do que ' '.join(argumentos) porque protege visualmente componentes com espaços e metacaracteres. Remova segredos antes de gerar o texto.
O módulo shlex precisa ser instalado com pip?
Não. Ele acompanha o Python. Basta import shlex. Bibliotecas externas podem ser úteis para construir CLIs, mas não são necessárias para separar ou citar argumentos.
Conclusão
shlex resolve um problema pequeno que causa muitos bugs: uma linha de comando não é apenas um conjunto de palavras separadas por espaço. Aspas, escapes e metacaracteres mudam como o texto é interpretado.
Use shlex.split() para converter linhas Unix confiáveis, shlex.join() para exibir listas e shlex.quote() somente para argumentos individuais em shells compatíveis. Para executar, mantenha subprocess com lista, shell=False, timeout, validação e allowlist sempre que possível.
Esse cuidado é útil em scripts de automação, ferramentas de terminal, pipelines de dados, backends e vagas de Python. Para continuar, leia como criar uma CLI com Python, aprofunde argparse e revise o guia de subprocess. A combinação das três ferramentas separa responsabilidades com clareza: a CLI valida, shlex analisa texto quando necessário e subprocess executa argumentos sem entregar controle desnecessário ao shell.
Equipe Python Brasil
Contribuidor do Python Brasil — Aprenda Python em Português