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

    Desarrollador configurando un servidor HTTPS y certificado TLS con Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    HTTPSServer: crea un servidor HTTPS local en Python

    Aprende HTTPSServer en Python para crear servicios HTTPS locales, configurar certificados, usar hilos y comprender sus límites.

    Ler mais

    Tempo de leitura: 4 minutos
    08/10/2026
    Archivos protegidos que representan extracción segura de TAR con Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    tarfile extraction_filter: extrae TAR con seguridad

    Aprende tarfile extraction_filter en Python para extraer archivos TAR con validación, seguridad y control de rutas.

    Ler mais

    Tempo de leitura: 6 minutos
    08/10/2026
    Código Python mostrando avisos controlados con catch_warnings
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    catch_warnings: captura warnings en pruebas Python

    Aprende catch_warnings en Python para capturar, probar y controlar avisos con filtros específicos y alcance seguro.

    Ler mais

    Tempo de leitura: 5 minutos
    07/10/2026
    Código Python que representa referencias persistentes de pickle
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    pickle persistent_id: serializa referencias externas

    Aprende pickle persistent_id en Python para referencias externas estables, validación, seguridad, rendimiento y compatibilidad.

    Ler mais

    Tempo de leitura: 5 minutos
    07/10/2026
    Código binario que representa buffers y vistas de memoria en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    memoryview.count: cuenta valores sin copiar buffers

    Aprende memoryview.count en Python para contar bytes y valores en buffers sin copias, con formatos, límites y buenas prácticas.

    Ler mais

    Tempo de leitura: 6 minutos
    06/10/2026
    Código Python y anotaciones de tipos
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    annotationlib: evita imports circulares en anotaciones

    Aprende annotationlib en Python para inspeccionar anotaciones diferidas, evitar imports circulares y crear herramientas seguras.

    Ler mais

    Tempo de leitura: 6 minutos
    06/10/2026