cmd — Procesadores de comando orientados a líneas¶
Propósito: | Crea procesadores de comandos orientados a líneas. |
---|
El módulo cmd
contiene una clase pública, Cmd
, diseñada para usarse
como clase base para shells interactivos y otros intérpretes de comandos. Por
defecto utiliza readline
para el manejo interactivo de solicitudes,
edición de línea de comandos y complementación de comandos.
Procesamiento de comandos¶
Un intérprete de comandos creado con cmd
usa un bucle para leer todas las
líneas de su entrada, analizarlas y luego enviar el comando a un controlador
de comandos apropiado. Las líneas de entrada se analizan en dos partes: el
comando y cualquier otro texto en la línea. Si el usuario ingresa foo bar
,
y la clase de intérprete incluye un método llamado do_foo()
, se ejecuta con
"bar"
como único argumento.
El marcador de fin de archivo se envía a do_EOF()
. Si un controlador de
comandos devuelve un valor verdadero, el programa saldrá limpiamente. Entonces
para dar una forma limpia de salir del intérprete, asegúrate de implementar
do_EOF()
y hacer que devuelva True.
Este sencillo programa de ejemplo admite el comando «greet»:
import cmd
class HelloWorld(cmd.Cmd):
def do_greet(self, line):
print("hello")
def do_EOF(self, line):
return True
if __name__ == '__main__':
HelloWorld().cmdloop()
Ejecutarlo interactivamente demuestra cómo se envían los comandos y muestra
algunas de las características incluidas en Cmd
.
$ python3 cmd_simple.py
(Cmd)
Lo primero que debes notar es el símbolo del sistema, (Cmd)
. La solicitud
se puede configurar a través del atributo prompt
. El valor de solicitud es
dinámico, y si un controlador de comando cambia el atributo de solicitud, el
nuevo valor se utiliza para consultar el siguiente comando.
Documented commands (type help <topic>):
========================================
help
Undocumented commands:
======================
EOF greet
El comando help
está integrado en Cmd
. Sin argumentos, help
muestra la lista de comandos disponibles. Si la entrada incluye un nombre de
comando, la salida es más detallada y está restringida a los detalles de ese
comando, cuando esté disponible.
Si el comando es greet
, se invoca do_greet()
para manejarlo:
(Cmd) greet
hello
Si la clase no incluye un controlador específico para un comando, se ejecuta el
método default()
con toda la línea de entrada como argumento. La
implementación incorporada de default()
informa un error.
(Cmd) foo
*** Unknown syntax: foo
Como do_EOF()
devuelve True, escribir Ctrl-D hace que el intérprete salga.
(Cmd) ^D$
No se imprime ninguna nueva línea al salir, por lo que los resultados son un poco confusos.
Argumentos de comando¶
Este ejemplo incluye algunas mejoras para eliminar algunas de las molestias y
agregar ayuda para el comando greet
.
import cmd
class HelloWorld(cmd.Cmd):
def do_greet(self, person):
"""greet [person]
Greet the named person"""
if person:
print("hi,", person)
else:
print('hi')
def do_EOF(self, line):
return True
def postloop(self):
print()
if __name__ == '__main__':
HelloWorld().cmdloop()
La cadena de documentación agregada a do_greet()
se convierte en el texto
de ayuda para el comando:
$ python3 cmd_arguments.py
(Cmd) help
Documented commands (type help <topic>):
========================================
greet help
Undocumented commands:
======================
EOF
(Cmd) help greet
greet [person]
Greet the named person
La salida muestra un argumento opcional para greet
, person
. Aunque el
argumento es opcional para el comando, existe una distinción entre el comando y
el método de devolución de llamada. El método siempre toma el argumento, pero
a veces el valor es una cadena vacía. Le corresponde al controlador de
comandos determinar si un argumento vacío es válido, o realizar cualquier
análisis y procesamiento adicional del comando. En este ejemplo, si se
proporciona el nombre de una persona, entonces el saludo es personalizado.
(Cmd) greet Alice
hi, Alice
(Cmd) greet
hi
Ya sea que el usuario proporcione un argumento o no, el valor pasado al controlador de comando no incluye el comando en sí. Eso simplifica el análisis en el controlador de comandos, especialmente si se necesitan múltiples argumentos.
Ayuda en vivo¶
En el ejemplo anterior, el formato del texto de ayuda deja algo que desear.
Como proviene de la cadena de documentación, retiene la sangría del archivo
fuente. La fuente podría cambiarse para eliminar el espacio en blanco
adicional, pero eso dejaría el código de la aplicación con un formato
deficiente. Una mejor solución es implementar un controlador de ayuda para el
comando greet
, llamado help_greet()
. Se llama al manejador de ayuda
para producir texto de ayuda para el comando nombrado.
# Set up gnureadline as readline if installed.
try:
import gnureadline
import sys
sys.modules['readline'] = gnureadline
except ImportError:
pass
import cmd
class HelloWorld(cmd.Cmd):
def do_greet(self, person):
if person:
print("hi,", person)
else:
print('hi')
def help_greet(self):
print('\n'.join([
'greet [person]',
'Greet the named person',
]))
def do_EOF(self, line):
return True
if __name__ == '__main__':
HelloWorld().cmdloop()
En este ejemplo, el texto es estático pero con un formato más agradable. También sería posible utilizar el estado del comando anterior para adaptar el contenido del texto de ayuda al contexto actual.
$ python3 cmd_do_help.py
(Cmd) help greet
greet [person]
Greet the named person
Depende del manejador de ayuda enviar el mensaje de ayuda y no simplemente devolver el texto de ayuda para su manejo en otro lugar.
Auto complementación¶
Cmd
incluye soporte para la complementación de comandos en función de los
nombres de los comandos con métodos de controlador. El usuario activa la
complementación presionando la tecla de tabulación en una solicitud de entrada.
Cuando son posibles varias finalizaciones, presionar la tecla dos veces imprime
una lista de opciones.
Nota
Las bibliotecas GNU necesarias para readline
no están disponibles en
todas las plataformas de forma predeterminada. En esos casos, la
complementación puede no funcionar. Consulta readline
para obtener
consejos sobre cómo instalar las bibliotecas necesarias si tu instalación de
Python no las tiene.
$ python3 cmd_do_help.py
(Cmd) <tab><tab>
EOF greet help
(Cmd) h<tab>
(Cmd) help
Una vez que se conoce el comando, la complementación del argumento se maneja
mediante métodos con el prefijo complete_
. Esto permite que los nuevos
controladores de complementación reúnan una lista de posibles complementaciones
utilizando criterios arbitrarios (es decir, consultar una base de datos o mirar
un archivo o directorio en el sistema de archivos). En este caso, el programa
tiene un conjunto codificado de «friends» que reciben un saludo menos formal
que los desconocidos nombrados o anónimos. Un programa real probablemente
guardaría la lista en algún lugar, la leería una vez y luego guardaría en caché
los contenidos para escanearlos según sea necesario.
# Set up gnureadline as readline if installed.
try:
import gnureadline
import sys
sys.modules['readline'] = gnureadline
except ImportError:
pass
import cmd
class HelloWorld(cmd.Cmd):
FRIENDS = ['Alice', 'Adam', 'Barbara', 'Bob']
def do_greet(self, person):
"Greet the person"
if person and person in self.FRIENDS:
greeting = 'hi, {}!'.format(person)
elif person:
greeting = 'hello, {}'.format(person)
else:
greeting = 'hello'
print(greeting)
def complete_greet(self, text, line, begidx, endidx):
if not text:
completions = self.FRIENDS[:]
else:
completions = [
f
for f in self.FRIENDS
if f.startswith(text)
]
return completions
def do_EOF(self, line):
return True
if __name__ == '__main__':
HelloWorld().cmdloop()
Cuando hay texto de entrada, complete_greet()
devuelve una lista de amigos
que coinciden. De lo contrario, se devuelve la lista completa de amigos.
$ python3 cmd_arg_completion.py
(Cmd) greet <tab><tab>
Adam Alice Barbara Bob
(Cmd) greet A<tab><tab>
Adam Alice
(Cmd) greet Ad<tab>
(Cmd) greet Adam
hi, Adam!
Si el nombre dado no está en la lista de amigos, se da el saludo formal.
(Cmd) greet Joe
hello, Joe
Anulación de métodos de clase base¶
Cmd
incluye varios métodos que pueden anularse como ganchos para realizar
acciones o alterar el comportamiento de la clase base. Este ejemplo no es
exhaustivo, pero contiene muchos de los métodos comúnmente útiles.
# Set up gnureadline as readline if installed.
try:
import gnureadline
import sys
sys.modules['readline'] = gnureadline
except ImportError:
pass
import cmd
class Illustrate(cmd.Cmd):
"Illustrate the base class method use."
def cmdloop(self, intro=None):
print('cmdloop({})'.format(intro))
return cmd.Cmd.cmdloop(self, intro)
def preloop(self):
print('preloop()')
def postloop(self):
print('postloop()')
def parseline(self, line):
print('parseline({!r}) =>'.format(line), end='')
ret = cmd.Cmd.parseline(self, line)
print(ret)
return ret
def onecmd(self, s):
print('onecmd({})'.format(s))
return cmd.Cmd.onecmd(self, s)
def emptyline(self):
print('emptyline()')
return cmd.Cmd.emptyline(self)
def default(self, line):
print('default({})'.format(line))
return cmd.Cmd.default(self, line)
def precmd(self, line):
print('precmd({})'.format(line))
return cmd.Cmd.precmd(self, line)
def postcmd(self, stop, line):
print('postcmd({}, {})'.format(stop, line))
return cmd.Cmd.postcmd(self, stop, line)
def do_greet(self, line):
print('hello,', line)
def do_EOF(self, line):
"Exit"
return True
if __name__ == '__main__':
Illustrate().cmdloop('Illustrating the methods of cmd.Cmd')
cmdloop()
es el bucle de procesamiento principal del intérprete. Por lo
general, no es necesario anularlo, ya que los ganchos preloop()
y
postloop()
están disponibles.
Cada iteración a través de cmdloop()
llama a onecmd()
para enviar el
comando a su controlador. La línea de entrada real se analiza con
parseline()
para crear una tupla que contenga el comando y la parte
restante de la línea.
Si la línea está vacía, se llama a emptyline()
. La implementación
predeterminada ejecuta el comando anterior nuevamente. Si la línea contiene un
comando, primero se llama a precmd()
, luego se busca el controlador y se
invoca. Si no se encuentra ninguno, se llama default()
en su lugar.
Finalmente se llama a postcmd()
.
Aquí hay una sesión de ejemplo con declaraciones print
agregadas:
$ python3 cmd_illustrate_methods.py
cmdloop(Illustrating the methods of cmd.Cmd)
preloop()
Illustrating the methods of cmd.Cmd
(Cmd) greet Bob
precmd(greet Bob)
onecmd(greet Bob)
parseline(greet Bob) => ('greet', 'Bob', 'greet Bob')
hello, Bob
postcmd(None, greet Bob)
(Cmd) ^Dprecmd(EOF)
onecmd(EOF)
parseline(EOF) => ('EOF', '', 'EOF')
postcmd(True, EOF)
postloop()
Configurar Cmd a través de atributos¶
Además de los métodos descritos anteriormente, existen varios atributos para el
controlar de comandos. prompt
se puede establecer en una cadena para
que se imprima cada vez que se le pide al usuario un nuevo comando.
intro
es el mensaje de «bienvenida» que se imprime al inicio del
programa. cmdloop()
toma un argumento para este valor, o se puede
establecer en la clase directamente. Al imprimir ayuda, los atributos
doc_header
, misc_header
, undoc_header
y ruler
se utilizan para formatear la salida.
import cmd
class HelloWorld(cmd.Cmd):
prompt = 'prompt: '
intro = "Simple command processor example."
doc_header = 'doc_header'
misc_header = 'misc_header'
undoc_header = 'undoc_header'
ruler = '-'
def do_prompt(self, line):
"Change the interactive prompt"
self.prompt = line + ': '
def do_EOF(self, line):
return True
if __name__ == '__main__':
HelloWorld().cmdloop()
Esta clase de ejemplo muestra un controlador de comandos que permite que el usuario controle la solicitud de la sesión interactiva.
$ python3 cmd_attributes.py
Simple command processor example.
prompt: prompt hello
hello: help
doc_header
----------
help prompt
undoc_header
------------
EOF
hello:
Ejecutar comandos de shell¶
Para complementar el procesamiento de comandos estándar, Cmd
incluye dos
prefijos de comandos especiales. Un signo de interrogación (?
) es
equivalente al comando help
incorporado, y se puede usar de la misma
manera. Un signo de exclamación (!
) se asigna a do_shell()
, y está
destinado para ejecutar otros comandos en el shell, como en este ejemplo.
import cmd
import subprocess
class ShellEnabled(cmd.Cmd):
last_output = ''
def do_shell(self, line):
"Run a shell command"
print("running shell command:", line)
sub_cmd = subprocess.Popen(line,
shell=True,
stdout=subprocess.PIPE)
output = sub_cmd.communicate()[0].decode('utf-8')
print(output)
self.last_output = output
def do_echo(self, line):
"""Print the input, replacing '$out' with
the output of the last shell command.
"""
# Obviously not robust
print(line.replace('$out', self.last_output))
def do_EOF(self, line):
return True
if __name__ == '__main__':
ShellEnabled().cmdloop()
Esta implementación del comando echo
reemplaza la cadena $out
en su
argumento con la salida del comando de shell anterior.
$ python3 cmd_do_shell.py
(Cmd) ?
Documented commands (type help <topic>):
========================================
echo help shell
Undocumented commands:
======================
EOF
(Cmd) ? shell
Run a shell command
(Cmd) ? echo
Print the input, replacing '$out' with
the output of the last shell command
(Cmd) shell pwd
running shell command: pwd
.../pymotw-3/source/cmd
(Cmd) ! pwd
running shell command: pwd
.../pymotw-3/source/cmd
(Cmd) echo $out
.../pymotw-3/source/cmd
Entradas Alternativas¶
Si bien el modo predeterminado para Cmd()
es interactuar con el usuario a
través de la biblioteca readline
, también es posible pasar una serie de
comandos a la entrada estándar utilizando la redirección de shell estándar de
Unix.
$ echo help | python3 cmd_do_help.py
(Cmd)
Documented commands (type help <topic>):
========================================
greet help
Undocumented commands:
======================
EOF
(Cmd)
Para que el programa lea un archivo de secuencia de comandos directamente,
pueden ser necesarios algunos otros cambios. Dado que readline
interactúa con el dispositivo terminal/tty, en lugar de la secuencia de entrada
estándar, debe deshabilitarse cuando la secuencia de comandos va a leer desde
un archivo. Además, para evitar imprimir solicitudes superfluas, la solicitud
se puede establecer en una cadena vacía. Este ejemplo muestra cómo abrir un
archivo y pasarlo como entrada a una versión modificada del ejemplo
HelloWorld
.
import cmd
class HelloWorld(cmd.Cmd):
# Disable rawinput module use
use_rawinput = False
# Do not show a prompt after each command read
prompt = ''
def do_greet(self, line):
print("hello,", line)
def do_EOF(self, line):
return True
if __name__ == '__main__':
import sys
with open(sys.argv[1], 'rt') as input:
HelloWorld(stdin=input).cmdloop()
Con use_rawinput
establecido en False y prompt
establecido en una
cadena vacía, la secuencia de comando se puede invocar en un archivo de entrada
con un comando en cada línea.
greet
greet Alice and Bob
La ejecución de la secuencia de comandos de ejemplo con la entrada de ejemplo produce el siguiente resultado.
$ python3 cmd_file.py cmd_file.txt
hello,
hello, Alice and Bob
Comandos de sys.argv¶
Los argumentos de la línea de comandos para el programa también se pueden
procesar como comandos para la clase de intérprete, en lugar de leer comandos
desde la consola o un archivo. Para usar los argumentos de la línea de
comandos, llama a onecmd()
directamente, como en este ejemplo.
import cmd
class InteractiveOrCommandLine(cmd.Cmd):
"""Accepts commands via the normal interactive
prompt or on the command line.
"""
def do_greet(self, line):
print('hello,', line)
def do_EOF(self, line):
return True
if __name__ == '__main__':
import sys
if len(sys.argv) > 1:
InteractiveOrCommandLine().onecmd(' '.join(sys.argv[1:]))
else:
InteractiveOrCommandLine().cmdloop()
Como onecmd()
toma una sola cadena como entrada, los argumentos del
programa deben unirse antes de pasarlos.
$ python3 cmd_argv.py greet Command-Line User
hello, Command-Line User
$ python3 cmd_argv.py
(Cmd) greet Interactive User
hello, Interactive User
(Cmd)
Ver también
- Documentación de la biblioteca estándar para cmd
- cmd2 – Reemplazo directo para
cmd
con características adicionales. - readline de GNU – La biblioteca readline de GNU proporciona funciones que permiten a los usuarios editar líneas de entrada a medida que se escriben.
readline
– La interfaz de la biblioteca estándar de Python para readline.subprocess
– Gestión de otros procesos y sus resultados.