2012-01-11 17 views
8

Al usar el cmd.Cmd de python para crear una CLI personalizada, ¿cómo le digo al controlador que cancele la línea actual y me proporcione una nueva solicitud?Mejor manejo de KeyboardInterrupt en el intérprete de línea de comandos cmd.Cmd

Aquí está un ejemplo mínimo:

# console_min.py 
# run: 'python console_min.py' 

import cmd, signal 

class Console(cmd.Cmd): 
    def __init__(self): 
     cmd.Cmd.__init__(self) 
     self.prompt = "[test] " 
     signal.signal(signal.SIGINT, handler) 

    def do_exit(self, args): 
     return -1 

    def do_EOF(self, args): 
     return self.do_exit(args) 

    def preloop(self): 
     cmd.Cmd.preloop(self) 
     self._hist = [] 
     self._locals = {} 
     self._globals = {} 

    def postloop(self): 
     cmd.Cmd.postloop(self) 
     print "Exiting ..." 

    def precmd(self, line): 
     self._hist += [ line.strip() ] 
     return line 

    def postcmd(self, stop, line): 
     return stop 

    def emptyline(self): 
     return cmd.Cmd.emptyline(self) 

    def handler(self, signum, frame): 
     # self.emptyline() does not work here 
     # return cmd.Cmd.emptyline(self) does not work here 
     print "caught ctrl+c, press return to continue" 

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

Además ayuda es muy apreciada.

Pregunta original y más detalles: [Actualmente, las siguientes sugerencias se han integrado en esta pregunta, sigue buscando una respuesta. Actualizado para corregir un error.]

Desde entonces he intentado mover el controlador a una función fuera del ciclo para ver si era más flexible, pero no parece ser así.

Estoy usando Python's cmd.Cmd module para crear mi propio intérprete de línea de comandos para administrar la interacción con algún software. A menudo presiono ctrl + c esperando el comportamiento popular similar a un intérprete de comandos al devolver un nuevo prompt sin actuar en cualquier comando que se haya tipeado. Sin embargo, simplemente sale. Intenté atrapar las excepciones KeyboardInterrupt en varios puntos del código (preloop, etc.) en vano. He leído un poco en sigints pero no sé exactamente cómo encaja aquí.

ACTUALIZACIÓN: A partir de las sugerencias a continuación, traté de implementar la señal y pude hacerlo de tal manera que ctrl + c ahora no sale de la CLI pero imprime el mensaje. Sin embargo, mi nuevo problema es que parece que no puedo decirle a la función del controlador (ver a continuación) que haga mucho más que imprimir. Me gustaría ctrl + c para abortar la línea actual y darme un nuevo aviso.

Respuesta

8

Actualmente estoy trabajando en la creación de una Shell mediante el uso º e Módulo Cmd. Me enfrenté con el mismo problema y encontré una solución.

Aquí está el código:

class Shell(Cmd, object) 
... 
    def cmdloop(self, intro=None): 
     print(self.intro) 
     while True: 
      try: 
       super(Shell, self).cmdloop(intro="") 
       self.postloop() 
       break 
      except KeyboardInterrupt: 
       print("^C") 
... 

Ahora usted tienen un controlador adecuado KeyboardInterrupt (también conocido como CTRL-C) dentro de la cáscara.

+0

Esto * parece * converger en una solución, pero no sé cómo integrarlo en mi propio código (arriba). Tengo que descubrir la línea 'super'. Necesito tratar de hacer que esto funcione en algún momento en el futuro. –

+0

En Python 2.x, 'cmd.Cmd' es una * clase de estilo antiguo *, por lo que' super' no funcionará. Ver [esta pregunta] (http://stackoverflow.com/questions/770134). –

+1

¿Por qué tienes que llamar 'postloop()' a ti mismo? –

-1

Puede tomar el CTRL-C signal con un signal handler. Si se agrega el código siguiente, el intérprete se niega a dejar de fumar por CTRL-C:

import signal 

def handler(signum, frame): 
    print 'Caught CTRL-C, press enter to continue' 

signal.signal(signal.SIGINT, handler) 

Si no desea presionar ENTER después de cada CTRL-C, simplemente dejar que el manejador no hacer nada, que atrapará la señal sin ningún efecto:

def handler(signum, frame): 
    """ just do nothing """ 
+0

* Simplemente no hacer nada * se puede expresar usando 'pass' en Python. –

-1

Se puede extraer el módulo signal: http://docs.python.org/library/signal.html

import signal 

oldSignal = None 

def handler(signum, frame): 
    global oldSignal 
    if signum == 2: 
     print "ctrl+c!!!!" 
    else: 
     oldSignal() 

oldSignal = signal.signal(signal.SIGINT, handler) 
3

En lugar de utilizar la gestión de señales, puede simplemente tomar el KeyboardInterrupt que aumenta cmd.Cmd.cmdloop(). Sin duda, puedes utilizar la gestión de señales, pero no es necesario.

Ejecute la llamada a cmdloop() en un ciclo while que se reinicia en una excepción KeyboardInterrupt pero termina correctamente debido a EOF.

import cmd 
import sys 

class Console(cmd.Cmd): 
    def do_EOF(self,line): 
     return True 
    def do_foo(self,line): 
     print "In foo" 
    def do_bar(self,line): 
     print "In bar" 
    def cmdloop_with_keyboard_interrupt(self): 
     doQuit = False 
     while doQuit != True: 
      try: 
       self.cmdloop() 
       doQuit = True 
      except KeyboardInterrupt: 
       sys.stdout.write('\n') 

console = Console() 

console.cmdloop_with_keyboard_interrupt() 

print 'Done!' 

Haciendo un Ctrl-C sólo imprime un nuevo símbolo en una nueva línea.

(Cmd) help 

Undocumented commands: 
====================== 
EOF bar foo help 

(Cmd) <----- ctrl-c pressed 
(Cmd) <------ctrl-c pressed 
(Cmd) ddasfjdfaslkdsafjkasdfjklsadfljk <---- ctrl-c pressed 
(Cmd) 
(Cmd) bar 
In bar 
(Cmd) ^DDone! 
+1

Esta es la solución adecuada. Podría establecer 'self.intro' en' None' si 'KeyboardInterrupt' fue capturado. –

+0

En lugar de crear un método 'cmdloop_with_keyboard_interrupt()', el bucle 'while doQuit' se puede colocar al final de' __init __() 'y luego la consola se ejecutará cuando se llame a Console(). –

+0

En lugar del elemento 'doQuit', sería un poco más simple usar' while True: 'y luego' break' después de que 'cmdloop()' se complete con éxito. – jamesdlin

-2

Sólo tiene que añadir esta dentro de la clase Console(cmd.Cmd):


    def cmdloop(self): 
     try: 
      cmd.Cmd.cmdloop(self) 
     except KeyboardInterrupt as e: 
      self.cmdloop() 

olvidar todas las otras cosas. Esto funciona. No crea bucles dentro de los bucles. A medida que capta KeyboardInterrupt llama al do_EOF pero solo ejecutará la primera línea; ya que su primera línea en do_EOF es return do_exit esto está bien.
do_exit llamadas postloop. Sin embargo, nuevamente, solo ejecuta la primera línea después de cmd.Cmd.postloop(self). En mi programa, esto es imprimir "\ n". Extrañamente, si usa SPAM ctrl + C, eventualmente verá que imprime la 2da línea, generalmente solo se imprime en la salida REAL (ctrl + Z, luego ingrese, o escriba exit).

+0

Este fue el enfoque que terminé tomando. El único problema posible es que si 'intro' fuera un atributo establecido en mi ejemplo anterior, imprimiría continuamente la introducción (inspirado en el comentario de @seb a continuación). Así que espero que esté bien editar su comentario para reflejar eso. –

+5

Recursion limit anyone? También hay un problema si dos 'KeyboardInterrupt's están demasiado cerca. – Veedrac

-2

Prefiero el método de la señal, pero con el pase. Realmente no me importa que el usuario tenga algo en mi entorno de shell.

import signal 

def handler(signum, frame): 
    pass 

signal.signal(signal.SIGINT, handler) 
+0

Esto no hace lo que OP quiere en absoluto, es decir, ignorar la entrada que estaba escrita actualmente en el prompt. –

0

En respuesta a la siguiente comentario en this answer:

Esto parece estar convergiendo en una solución, pero no sé cómo integrarlo en mi propio código (arriba). Tengo que descubrir la línea 'super'. Necesito tratar de hacer que esto funcione en algún momento en el futuro.

super() funcionaría en esta respuesta si usted tiene su clase extender object además de cmd.Cmd. De esta manera:

class Console(cmd.Cmd, object): 
Cuestiones relacionadas