cmd no Python: crie consoles interativos

Publicado em: 12/08/2026
Tempo de leitura: 5 minutos
Janela de terminal representando console interativo criado com cmd no Python

O módulo cmd no Python fornece uma estrutura pronta para criar interpretadores de comandos orientados a linhas. Em vez de escrever manualmente um loop com input(), separar o nome do comando, procurar uma função e implementar ajuda, você herda de cmd.Cmd e define métodos com o prefixo do_.

Esse modelo é útil em consoles administrativos, ferramentas de teste, protótipos, simuladores, utilitários de banco de dados e interfaces locais para serviços. Ele não substitui uma CLI tradicional baseada em argumentos nem uma aplicação gráfica, mas oferece uma experiência interativa persistente, com prompt, histórico e autocompletar quando o ambiente suporta readline.

Primeiro console com cmd.Cmd

O exemplo mínimo define um prompt, uma mensagem de abertura e dois comandos.

import cmd

class Console(cmd.Cmd):
    intro = 'Console iniciado. Digite help ou ?.'
    prompt = '(app) '

    def do_status(self, arg):
        'Mostra o estado atual: status'
        print('Sistema operacional')

    def do_sair(self, arg):
        'Encerra o console: sair'
        print('Até logo')
        return True

if __name__ == '__main__':
    Console().cmdloop()

O nome depois de do_ vira o comando. O valor retornado pelo método segue para postcmd(); quando o resultado final é verdadeiro, cmdloop() termina.

Como o despacho funciona

Ao receber uma linha, a classe identifica o prefixo inicial como nome do comando e passa o restante da linha como uma única string ao método correspondente. Assim, exportar clientes.csv --compacto chama do_exportar() com clientes.csv --compacto.

def do_echo(self, arg):
    print(arg)

O módulo não interpreta automaticamente opções complexas. Você pode usar shlex.split() para preservar aspas ou integrar argparse dentro de cada comando. O artigo sobre shlex no Python mostra como separar argumentos com segurança.

Ajuda automática

Toda subclasse herda o comando help. Se um método do_status() possui docstring, ela aparece em help status. Também é possível criar um método help_status() para uma explicação mais longa.

def do_backup(self, arg):
    'Cria um backup: backup DESTINO'
    ...

def help_backup(self):
    print('Uso: backup DESTINO')
    print('Copia dados para uma pasta aprovada.')

Sem argumento, help lista tópicos documentados, não documentados e auxiliares. Os cabeçalhos podem ser personalizados com doc_header, undoc_header e misc_header.

Autocompletar comandos e argumentos

Quando readline está disponível, os nomes dos comandos são completados automaticamente. Para argumentos específicos, implemente complete_nome().

AMBIENTES = ['dev', 'staging', 'producao']

def complete_ambiente(self, text, line, begidx, endidx):
    return [nome for nome in AMBIENTES if nome.startswith(text)]

def do_ambiente(self, arg):
    if arg not in AMBIENTES:
        print('Ambiente inválido')
        return
    self.ambiente = arg

Os parâmetros line, begidx e endidx permitem alterar sugestões conforme a posição. Não retorne segredos, caminhos sensíveis ou dados que o usuário não está autorizado a consultar.

Validar argumentos

Todo texto digitado deve ser considerado entrada não confiável. Separe argumentos, valide quantidade, tipos, intervalos, caminhos e permissões. Não encaminhe o conteúdo diretamente a eval(), exec() ou a um shell.

import shlex

class Console(cmd.Cmd):
    def do_usuario(self, arg):
        try:
            tokens = shlex.split(arg)
        except ValueError as erro:
            print(f'Entrada inválida: {erro}')
            return

        if len(tokens) != 1:
            print('Uso: usuario NOME')
            return

        nome = tokens[0]
        if not nome.isidentifier():
            print('Nome inválido')
            return
        selecionar_usuario(nome)

Uma allowlist de ações e valores é preferível a filtros que tentam remover caracteres perigosos.

Comando desconhecido com default()

Quando nenhum método do_* corresponde ao prefixo, default() é chamado. Você pode oferecer uma mensagem melhor ou sugerir comandos semelhantes.

import difflib

COMMANDS = ['status', 'backup', 'usuario', 'sair']

def default(self, line):
    nome = line.split(maxsplit=1)[0]
    sugestoes = difflib.get_close_matches(nome, COMMANDS, n=1)
    if sugestoes:
        print(f'Comando desconhecido. Talvez: {sugestoes[0]}')
    else:
        print('Comando desconhecido. Digite help.')

Evite usar default() para executar comandos do sistema. Isso transforma um console restrito em um shell aberto.

Controlar linhas vazias

O comportamento padrão de emptyline() repete o último comando não vazio. Isso pode ser perigoso para ações destrutivas, como apagar dados ou repetir uma cobrança. Sobrescreva o método para não fazer nada.

def emptyline(self):
    pass

Outra opção é repetir apenas comandos explicitamente marcados como idempotentes, mas essa regra precisa estar documentada e testada.

Hooks precmd e postcmd

precmd() recebe a linha antes do despacho e pode normalizá-la, registrar auditoria ou bloquear comandos. postcmd() roda depois e pode alterar a decisão de encerrar.

def precmd(self, line):
    linha = line.strip()
    registrar_tentativa(self.usuario, linha)
    return linha

def postcmd(self, stop, line):
    registrar_resultado(self.usuario, line, stop)
    return stop

Não registre senhas, tokens ou argumentos sensíveis. Para credenciais no terminal, use técnicas do guia sobre leitura segura de senhas.

preloop e postloop

preloop() executa uma vez antes do primeiro prompt. É um bom local para abrir uma conexão, carregar configuração ou verificar permissões. postloop() roda quando o console encerra e deve liberar recursos.

def preloop(self):
    self.conexao = conectar()

def postloop(self):
    self.conexao.close()

Use context managers ou ExitStack quando houver vários recursos. Também trate interrupções e exceções para garantir a limpeza mesmo quando o usuário pressiona Ctrl+C.

Executar uma linha com onecmd()

onecmd() interpreta uma string como se tivesse sido digitada. Isso é útil em testes e integrações.

console = Console()
resultado = console.onecmd('status')

Normalmente, não é preciso sobrescrever onecmd(); os hooks são pontos mais seguros para personalização. Em testes, capture stdout para verificar a saída.

Fila de comandos com cmdqueue

cmdqueue é uma lista de linhas processadas antes de solicitar nova entrada. Ela permite scripts, macros e reprodução de sessões.

console = Console()
console.cmdqueue.extend([
    'status',
    'ambiente staging',
    'sair',
])
console.cmdloop()

Ao carregar comandos de arquivo, limite tamanho e quantidade, valide caminhos e não presuma que o conteúdo é confiável. Para processar arquivos linha por linha, consulte fileinput no Python.

Entrada e saída customizadas

O construtor aceita stdin e stdout. Para que um stdin fornecido seja usado, defina use_rawinput=False.

from io import StringIO

entrada = StringIO('status\nsair\n')
saida = StringIO()

console = Console(stdin=entrada, stdout=saida)
console.use_rawinput = False
console.cmdloop()
print(saida.getvalue())

Escreva em self.stdout, e não diretamente em print(), quando quiser que a redireção funcione de forma consistente.

Tratamento de EOF e interrupções

O fim da entrada chega como o comando especial EOF. Implemente do_EOF() para encerrar corretamente.

def do_EOF(self, arg):
    self.stdout.write('\nEncerrando\n')
    return True

Para Ctrl+C, envolva o loop ou personalize a estratégia, garantindo que a aplicação não deixe transações abertas. Mensagens claras ajudam a distinguir cancelamento de erro.

Autorização por comando

Consoles administrativos precisam de autorização, não apenas autenticação. Verifique o papel do usuário em cada ação crítica.

def do_apagar_cache(self, arg):
    if 'admin' not in self.permissoes:
        self.stdout.write('Acesso negado\n')
        return
    confirmar_e_apagar_cache()

Não dependa da ocultação do comando na ajuda. Um usuário pode chamar o método diretamente pela linha.

Testar o console

Teste comandos válidos, desconhecidos, argumentos incompletos, EOF, linhas vazias, exceções e permissões. Use StringIO para entradas e saídas determinísticas.

def testar_status():
    entrada = StringIO('status\nEOF\n')
    saida = StringIO()
    console = Console(stdin=entrada, stdout=saida)
    console.use_rawinput = False
    console.cmdloop()
    assert 'operacional' in saida.getvalue()

Evite dependências reais de rede e banco nos testes unitários. Injete serviços falsos.

Erros frequentes

  • Executar argumentos com shell=True.
  • Deixar emptyline() repetir ações destrutivas.
  • Não implementar do_EOF().
  • Escrever em print() e ignorar self.stdout.
  • Exibir segredos no autocompletar.
  • Confundir comando oculto com comando autorizado.
  • Carregar filas de arquivos sem limites.

Boas práticas

  • Mantenha cada do_* pequeno e delegue a serviços.
  • Valide argumentos com regras explícitas.
  • Use docstrings e ajuda consistente.
  • Desative repetição de linha vazia quando necessário.
  • Registre auditoria sem dados sensíveis.
  • Teste com streams em memória.
  • Libere recursos em postloop().

Conclusão

O cmd no Python reduz o trabalho necessário para criar consoles interativos, oferecendo despacho de comandos, ajuda, histórico, autocompletar, hooks e filas. Ele é excelente para ferramentas internas e protótipos controlados.

A segurança depende da camada que você constrói: valide argumentos, autorize ações, evite shells e trate arquivos de comandos como entrada não confiável. Consulte a documentação oficial do cmd e a documentação do readline.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Conteúdo do artigo

    Artigos relacionados

    Terminal interativo representando um REPL customizado com o módulo code no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    code no Python: crie um REPL customizado

    Aprenda o módulo code no Python para criar REPLs customizados, controlar namespaces, prompts, saída, blocos incompletos e encerramento seguro.

    Ler mais

    Tempo de leitura: 6 minutos
    12/08/2026
    Código de aplicação web representando WSGI com wsgiref no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    wsgiref no Python: aplicações WSGI

    Aprenda wsgiref no Python para criar e validar aplicações WSGI, testar environ, headers, rotas e servidores locais sem usar em

    Ler mais

    Tempo de leitura: 5 minutos
    12/08/2026
    Protocolo seguro na internet representando preparação Unicode com stringprep no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    stringprep no Python: prepare Unicode

    Aprenda stringprep no Python para aplicar tabelas do RFC 3454, mapear Unicode, bloquear caracteres proibidos e validar regras bidirecionais.

    Ler mais

    Tempo de leitura: 7 minutos
    12/08/2026
    Rede de conexões representando I/O não bloqueante com selectors no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    selectors no Python: I/O não bloqueante

    Aprenda selectors no Python para monitorar vários sockets, eventos de leitura e escrita, timeouts e conexões não bloqueantes com segurança.

    Ler mais

    Tempo de leitura: 5 minutos
    11/08/2026
    Fluxo de dados em rede representando contexto assíncrono com contextvars no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    contextvars no Python: contexto assíncrono

    Aprenda contextvars no Python para armazenar estado por tarefa, evitar vazamentos em asyncio, copiar contextos e restaurar valores com tokens.

    Ler mais

    Tempo de leitura: 6 minutos
    11/08/2026
    Código de programação representando operações como funções com operator no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    operator no Python: operações como funções

    Aprenda operator no Python para usar operações como funções, ordenar campos, acessar itens, chamar métodos e trabalhar com pipelines funcionais.

    Ler mais

    Tempo de leitura: 5 minutos
    11/08/2026