---
title: "shlex em Python: argumentos de shell sem quebrar comandos"
url: "https://python.dev.br/blog/python-shlex-comandos-shell-seguros/"
markdown_url: "https://python.dev.br/blog/python-shlex-comandos-shell-seguros.MD"
description: "Aprenda shlex em Python para separar comandos, citar argumentos com segurança, ler arquivos de configuração e evitar erros comuns com shell=True."
date: "2026-07-30"
author: "Equipe Python Brasil"
---

# 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:

```text
python gerar_relatorio.py --cliente "Loja São José" --saida "relatórios/julho 2026.csv"
```

Usar `str.split()` produz o resultado errado:

```python
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
[
    "python",
    "gerar_relatorio.py",
    "--cliente",
    '"Loja',
    "São",
    'José"',
]
```

`shlex.split()` entende aspas e escapes em uma sintaxe semelhante à de shells Unix:

```python
import shlex

comando = 'python gerar_relatorio.py --cliente "Loja São José"'
argumentos = shlex.split(comando)

print(argumentos)
```

Resultado:

```python
[
    "python",
    "gerar_relatorio.py",
    "--cliente",
    "Loja São José",
]
```

Essa lista é exatamente o formato esperado por `subprocess.run()`:

```python
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](/blog/python-subprocess-comandos-externos/).

## shlex.split: de linha de comando para lista

A assinatura mais comum é simples:

```python
import shlex

argumentos = shlex.split('backup --origem "/dados da empresa" --compactar')
print(argumentos)
```

Saída:

```python
["backup", "--origem", "/dados da empresa", "--compactar"]
```

A função também entende aspas simples:

```python
shlex.split("enviar --mensagem 'Processamento concluído com sucesso'")
```

E escapes em modo POSIX:

```python
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:

```python
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:

```python
import subprocess

arquivo = input("Arquivo: ")
subprocess.run(f"cat {arquivo}", shell=True, check=True)
```

Se a entrada for:

```text
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:

```python
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:

```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`](/blog/python-pathlib-manipulacao-caminhos-arquivos/) 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**.

```python
import shlex

arquivo = "Relatórios/resultado final.csv"
seguro = shlex.quote(arquivo)

print(seguro)
```

Saída típica:

```text
'Relatórios/resultado final.csv'
```

Com uma entrada mais problemática:

```python
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:

```python
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:

```python
# 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:

```python
import shlex

argumentos = [
    "python",
    "gerar_relatorio.py",
    "--cliente",
    "Loja São José",
    "--saida",
    "relatórios/julho 2026.csv",
]

print(shlex.join(argumentos))
```

Saída:

```text
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.

```python
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:

```text
cliente-api --token '[OCULTO]' listar
```

O artigo sobre [logging em Python](/blog/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.

```python
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:

```python
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:

1. o usuário escolhe uma tarefa conhecida, não um executável arbitrário;
2. `shlex.split()` preserva argumentos com espaços;
3. `subprocess` recebe uma lista e não invoca shell;
4. cada tarefa tem timeout;
5. `check=True` transforma código de saída diferente de zero em erro;
6. o comando exibido vem de `shlex.join()`;
7. 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`](/blog/argparse-python-guia-completo/), 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`:

```text
# nome formato pasta
vendas csv "dados/vendas de julho"
estoque json dados/estoque
```

Leitura:

```python
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:

```python
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:

```python
import shlex

lexer = shlex.shlex(
    "gerar | comprimir && publicar",
    posix=True,
    punctuation_chars=True,
)
lexer.whitespace_split = True

print(list(lexer))
```

Resultado aproximado:

```python
["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

```python
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

```python
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

```python
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:

```python
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:

```python
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:

```python
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](/blog/testes-unitarios-python/) 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](/vagas/). Para continuar, leia [como criar uma CLI com Python](/blog/criando-cli-com-python/), aprofunde [`argparse`](/blog/argparse-python-guia-completo/) e revise o guia de [`subprocess`](/blog/python-subprocess-comandos-externos/). 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.
