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 = argOs 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):
passOutra 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 stopNã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 TruePara 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 ignorarself.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.







