cmd en Python: crea consolas interactivas

Publicado el: 12/08/2026
Tempo de leitura: 5 minutos
Ventana de terminal que representa una consola interactiva creada con cmd en Python

El módulo cmd en Python ofrece una estructura lista para crear intérpretes de comandos orientados a líneas. En lugar de escribir manualmente un bucle con input(), extraer el nombre de la acción, buscar una función e implementar ayuda, heredas de cmd.Cmd y defines métodos cuyo nombre comienza con do_.

Este modelo resulta útil en consolas administrativas, herramientas de pruebas, prototipos, simuladores, utilidades de base de datos e interfaces locales para servicios. No sustituye una CLI tradicional basada en argumentos ni una aplicación gráfica, pero proporciona una sesión interactiva persistente con prompt, historial y autocompletado cuando readline está disponible.

Primera consola con cmd.Cmd

import cmd

class Consola(cmd.Cmd):
    intro = 'Consola iniciada. Escribe help o ?.'
    prompt = '(app) '

    def do_estado(self, arg):
        'Muestra el estado actual: estado'
        print('Sistema operativo')

    def do_salir(self, arg):
        'Cierra la consola: salir'
        print('Hasta luego')
        return True

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

El texto después de do_ se convierte en el nombre del comando. El valor devuelto llega a postcmd(); cuando el resultado final es verdadero, cmdloop() termina.

Cómo funciona el despacho

La clase interpreta el primer prefijo de la línea como comando y entrega el resto como una única cadena. Por tanto, exportar clientes.csv --compacto llama a do_exportar() con clientes.csv --compacto.

def do_echo(self, arg):
    self.stdout.write(arg + '\n')

El framework no analiza opciones complejas automáticamente. Utiliza shlex.split() para respetar comillas o integra argparse dentro de cada acción. La guía de shlex en Python explica esta tokenización.

Ayuda incorporada

Toda subclase hereda el comando help. La docstring de do_estado() aparece en help estado. Para una explicación más extensa, implementa help_estado().

def do_backup(self, arg):
    'Crea un backup: backup DESTINO'
    ...

def help_backup(self):
    self.stdout.write('Uso: backup DESTINO\n')
    self.stdout.write('Copia datos a una carpeta aprobada.\n')

Sin argumento, la ayuda lista temas documentados, no documentados y auxiliares. Los encabezados pueden personalizarse con doc_header, undoc_header y misc_header.

Autocompletar comandos y argumentos

Cuando readline está disponible, los nombres de comandos se completan automáticamente. Para argumentos específicos, implementa complete_nombre().

ENTORNOS = ['dev', 'staging', 'produccion']

def complete_entorno(self, text, line, begidx, endidx):
    return [nombre for nombre in ENTORNOS if nombre.startswith(text)]

def do_entorno(self, arg):
    if arg not in ENTORNOS:
        self.stdout.write('Entorno inválido\n')
        return
    self.entorno = arg

Los parámetros de línea e índices permiten variar las sugerencias según la posición. No muestres secretos, rutas restringidas ni recursos que el usuario no pueda consultar.

Validar todos los argumentos

La entrada del prompt no es confiable. Separa tokens, valida cantidad, tipos, rangos, rutas y permisos. Nunca reenvíes el texto directamente a eval(), exec() o un shell.

import shlex

class Consola(cmd.Cmd):
    def do_usuario(self, arg):
        try:
            tokens = shlex.split(arg)
        except ValueError as error:
            self.stdout.write(f'Entrada inválida: {error}\n')
            return

        if len(tokens) != 1:
            self.stdout.write('Uso: usuario NOMBRE\n')
            return

        nombre = tokens[0]
        if not nombre.isidentifier():
            self.stdout.write('Nombre inválido\n')
            return
        seleccionar_usuario(nombre)

Una lista permitida de acciones y valores es más segura que intentar eliminar caracteres peligrosos.

Comandos desconocidos con default()

Si no existe un método do_*, se llama a default(). Úsalo para un error claro o una sugerencia.

import difflib

COMANDOS = ['estado', 'backup', 'usuario', 'salir']

def default(self, line):
    nombre = line.split(maxsplit=1)[0]
    opciones = difflib.get_close_matches(nombre, COMANDOS, n=1)
    if opciones:
        self.stdout.write(f'Comando desconocido. Quizá: {opciones[0]}\n')
    else:
        self.stdout.write('Comando desconocido. Escribe help.\n')

No uses default() para ejecutar comandos arbitrarios del sistema. Eso convierte una herramienta restringida en un shell abierto.

Comportamiento de líneas vacías

Por defecto, emptyline() repite el último comando no vacío. Puede ser peligroso para acciones destructivas, cobros o despliegues. Sobrescribe el método para no hacer nada.

def emptyline(self):
    pass

También puedes repetir solo acciones conocidas como idempotentes, pero la regla debe estar documentada y probada.

Hooks precmd() y postcmd()

precmd() recibe la línea antes del despacho y puede normalizarla, auditarla o bloquearla. postcmd() se ejecuta después y puede modificar la decisión de finalizar.

def precmd(self, line):
    normalizada = line.strip()
    registrar_intento(self.usuario, normalizada)
    return normalizada

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

No registres contraseñas, tokens ni argumentos sensibles. Para credenciales, utiliza técnicas de lectura segura en el terminal.

preloop() y postloop()

preloop() se ejecuta una vez antes del primer prompt. Sirve para abrir conexiones, cargar configuración o comprobar permisos. postloop() debe liberar recursos.

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

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

Usa context managers o ExitStack con varios recursos y trata interrupciones para que Ctrl+C no deje locks o transacciones abiertas.

Ejecutar una línea con onecmd()

onecmd() interpreta una cadena como si hubiera sido escrita por el usuario. Resulta útil en pruebas e integraciones.

consola = Consola()
resultado = consola.onecmd('estado')

Normalmente no necesitas sobrescribirlo. Los hooks antes y después del comando son puntos de extensión más seguros.

Cola de comandos con cmdqueue

cmdqueue contiene líneas procesadas antes de solicitar nueva entrada. Permite scripts, macros y reproducción.

consola = Consola()
consola.cmdqueue.extend([
    'estado',
    'entorno staging',
    'salir',
])
consola.cmdloop()

Cuando las líneas provienen de un archivo, limita tamaño y cantidad, valida la ruta y trata el contenido como no confiable. Para archivos por líneas, consulta fileinput en Python.

Streams de entrada y salida

El constructor acepta stdin y stdout. Para usar un stream de entrada proporcionado, configura use_rawinput=False.

from io import StringIO

entrada = StringIO('estado\nsalir\n')
salida = StringIO()

consola = Consola(stdin=entrada, stdout=salida)
consola.use_rawinput = False
consola.cmdloop()
print(salida.getvalue())

Escribe en self.stdout en lugar de utilizar siempre print() cuando necesites redirección consistente.

EOF e interrupciones

El fin de archivo se entrega como el comando especial EOF. Implementa do_EOF() para cerrar limpiamente.

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

Trata KeyboardInterrupt en un límite apropiado y garantiza que la limpieza se ejecuta. Distingue cancelación de error de comando.

Autorización por comando

Una consola administrativa necesita autorización, no solo autenticación. Verifica el rol para cada acción crítica.

def do_limpiar_cache(self, arg):
    if 'admin' not in self.permisos:
        self.stdout.write('Acceso denegado\n')
        return
    confirmar_y_limpiar_cache()

Ocultar una acción de la ayuda no es control de acceso. El usuario aún puede escribir su nombre.

Pruebas con streams en memoria

Prueba comandos válidos, desconocidos, argumentos incompletos, EOF, líneas vacías, excepciones y permisos.

def test_estado():
    entrada = StringIO('estado\nEOF\n')
    salida = StringIO()
    consola = Consola(stdin=entrada, stdout=salida)
    consola.use_rawinput = False
    consola.cmdloop()
    assert 'operativo' in salida.getvalue()

Inyecta servicios falsos en lugar de conectar bases de datos o redes reales en pruebas unitarias.

Errores frecuentes

  • Ejecutar argumentos con shell=True.
  • Permitir que una línea vacía repita acciones destructivas.
  • Olvidar do_EOF().
  • Ignorar self.stdout.
  • Mostrar secretos en autocompletado.
  • Confundir comandos ocultos con autorizados.
  • Cargar archivos de comandos sin límites.

Buenas prácticas

  • Mantén pequeños los métodos do_*.
  • Delega la lógica a servicios.
  • Valida argumentos explícitamente.
  • Ofrece ayuda y ejemplos consistentes.
  • Audita sin datos sensibles.
  • Prueba con streams en memoria.
  • Libera recursos en postloop().

Conclusión

cmd en Python reduce el código repetitivo de las consolas interactivas. Incluye despacho, ayuda, historial, autocompletado, hooks de ciclo de vida, streams y colas.

La seguridad depende de tu diseño: valida datos, autoriza acciones, evita shells y desconfía de archivos de comandos. Consulta la documentación oficial de cmd y la documentación de readline.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Contenido del artículo

    Artículos relacionados

    Terminal interactivo que representa un REPL personalizado creado con el módulo code en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    code en Python: crea un REPL personalizado

    Aprende el módulo code en Python para crear REPLs personalizados, controlar namespaces, prompts, salida, bloques incompletos y cierre local.

    Ler mais

    Tempo de leitura: 6 minutos
    12/08/2026
    Código de aplicación web que representa WSGI con wsgiref en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    wsgiref en Python: aplicaciones WSGI

    Aprende wsgiref en Python para crear y validar aplicaciones WSGI, probar environ y headers, enrutar solicitudes y ejecutar un servidor

    Ler mais

    Tempo de leitura: 4 minutos
    12/08/2026
    Protocolo seguro de Internet que representa preparación Unicode con stringprep en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    stringprep en Python: prepara Unicode

    Aprende stringprep en Python para aplicar tablas RFC 3454, mapear Unicode, rechazar caracteres prohibidos y validar reglas bidireccionales.

    Ler mais

    Tempo de leitura: 5 minutos
    12/08/2026
    Red de conexiones que representa I/O no bloqueante con selectors en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    selectors en Python: I/O no bloqueante

    Aprende selectors en Python para monitorizar muchos sockets, eventos de lectura y escritura, timeouts y conexiones no bloqueantes con seguridad.

    Ler mais

    Tempo de leitura: 4 minutos
    11/08/2026
    Flujo de datos en red que representa contexto asíncrono con contextvars en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    contextvars en Python: contexto asíncrono

    Aprende contextvars en Python para guardar estado por tarea, evitar fugas en asyncio, copiar contextos y restaurar valores con tokens.

    Ler mais

    Tempo de leitura: 5 minutos
    11/08/2026
    Código de programación que representa operaciones como funciones con operator en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    operator en Python: operaciones como funciones

    Aprende operator en Python para usar operaciones como funciones, ordenar campos, acceder a elementos, llamar métodos y crear pipelines claros.

    Ler mais

    Tempo de leitura: 4 minutos
    11/08/2026