2016-05-22 22 views
5

Ho scritto un'applicazione Python 3.5 utilizzando il modulo cmd. L'ultima cosa che vorrei implementare è la corretta gestione del segnale CTRL-C (sigint). Desidero a comportarsi più o meno come Bash fa:Gestire CTRL-C nel modulo cmd Python

  • stampa^C nel punto del cursore è
  • chiaro il buffer in modo che il testo inserito viene eliminato
  • saltare al successivo linea, stampare la richiesta, e attendere per l'ingresso

in sostanza:

/test $ bla bla bla| 
# user types CTRL-C 
/test $ bla bla bla^C 
/test $ 

Qui è semplificato codice come eseguibile sample:

import cmd 
import signal 


class TestShell(cmd.Cmd): 
    def __init__(self): 
     super().__init__() 

     self.prompt = '$ ' 

     signal.signal(signal.SIGINT, handler=self._ctrl_c_handler) 
     self._interrupted = False 

    def _ctrl_c_handler(self, signal, frame): 
     print('^C') 
     self._interrupted = True 

    def precmd(self, line): 
     if self._interrupted: 
      self._interrupted = False 
      return '' 

     if line == 'EOF': 
      return 'exit' 

     return line 

    def emptyline(self): 
     pass 

    def do_exit(self, line): 
     return True 


TestShell().cmdloop() 

Questo funziona quasi. Quando premo CTRL-C,^C viene stampato sul cursore, ma devo ancora premere Invio. Quindi, il metodo notifica il flag self._interrupted impostato dal gestore e restituisce una riga vuota. Questo è il massimo che potrei prendere, ma mi piacerebbe in qualche modo non dover premere quel tasto.

Immagino di dover in qualche modo forzare il input() a tornare, qualcuno ha idee?

+0

Sarebbe utile avere un piccolo campione eseguibile. Senza eseguire il tuo codice come lo stai attualmente facendo, scriverebbe un ''\ n'' o vuoto per' stdout' è sufficiente? Questo è il modo in cui il gestore del segnale è scattato virare una nuova riga alla fine di ''^ C'' – tijko

+1

@tijko Modificato il post sostituendo lo snippet con un campione eseguibile. Ho provato a scrivere una nuova riga su stdout (in realtà è eseguita di default dal metodo di stampa), ma non funziona, e perché dovrebbe? Richiederebbe che lo stdin sia alimentato dallo stdout, il che non è il caso, non è collegato. – wujek

+0

Sì, hai ragione, non stavo pensando in termini di prompt della riga di comando. @ DanGetz ha una risposta che soddisferà le tue esigenze credo. Anche per la pulizia, non è necessario impostare il gestore di segnale o impostare le bandiere. Dopo il tuo commento, ho iniziato a pensare anche a come reindirizzare lo stdin, puoi adattarlo a qualsiasi necessità. – tijko

risposta

3

Ho trovato alcuni modi hacky per ottenere il comportamento desiderato con Ctrl-C.

Set use_rawinput=False e sostituire stdin

Questo uno stick (più o meno ...) per l'interfaccia pubblica di cmd.Cmd. Sfortunatamente, disabilita il supporto alla lettura.

È possibile impostare use_rawinput su false e passare un oggetto simile a file diverso per sostituire stdin in Cmd.__init__(). In pratica, su questo oggetto viene chiamato solo readline(). Così si può creare un wrapper per stdin che cattura l'KeyboardInterrupt ed esegue il comportamento che volete invece:

class _Wrapper: 

    def __init__(self, fd): 
     self.fd = fd 

    def readline(self, *args): 
     try: 
      return self.fd.readline(*args) 
     except KeyboardInterrupt: 
      print() 
      return '\n' 


class TestShell(cmd.Cmd): 

    def __init__(self): 
     super().__init__(stdin=_Wrapper(sys.stdin)) 
     self.use_rawinput = False 
     self.prompt = '$ ' 

    def precmd(self, line): 
     if line == 'EOF': 
      return 'exit' 
     return line 

    def emptyline(self): 
     pass 

    def do_exit(self, line): 
     return True 


TestShell().cmdloop() 

Quando eseguo questo sul mio terminale, Ctrl-C mostra ^C e passa a una nuova linea.

scimmia-patch input()

Se si desidera che i risultati di input(), tranne che si vuole un comportamento diverso per Ctrl-C, un modo per farlo sarebbe quello di utilizzare una funzione diversa, invece di input():

def my_input(*args): # input() takes either no args or one non-keyword arg 
    try: 
     return input(*args) 
    except KeyboardInterrupt: 
     print('^C') # on my system, input() doesn't show the ^C 
     return '\n' 

Tuttavia, se si imposta semplicemente input = my_input, si ottiene la ricorsione infinita perché my_input() chiamerà input(), che ora è di per sé.Ma questo è risolvibile, e si può patchare il __builtins__ dizionario nel modulo cmd di utilizzare il input() metodo modificato durante Cmd.cmdloop():

def input_swallowing_interrupt(_input): 
    def _input_swallowing_interrupt(*args): 
     try: 
      return _input(*args) 
     except KeyboardInterrupt: 
      print('^C') 
      return '\n' 
    return _input_swallowing_interrupt 


class TestShell(cmd.Cmd): 

    def cmdloop(self, *args, **kwargs): 
     old_input_fn = cmd.__builtins__['input'] 
     cmd.__builtins__['input'] = input_swallowing_interrupt(old_input_fn) 
     try: 
      super().cmdloop(*args, **kwargs) 
     finally: 
      cmd.__builtins__['input'] = old_input_fn 

    # ... 

Si noti che questo cambia input() per tutti Cmd oggetti, non solo TestShell oggetti. Se questo non è accettabile per voi, si potrebbe ...

copiare il codice sorgente e modificarlo Cmd.cmdloop()

Dal momento che stai sottoclasse, è possibile effettuare cmdloop() fare quello che vuoi. "Tutto ciò che vuoi" potrebbe includere la copia di parti di Cmd.cmdloop() e la riscrittura di altre. Sostituisci la chiamata a input() con una chiamata a un'altra funzione oppure prendi e gestisci KeyboardInterrupt proprio lì nella tua riscritta cmdloop().

Se si ha paura dell'implementazione sottostante che cambia con le nuove versioni di Python, è possibile copiare l'intero modulo cmd in un nuovo modulo e modificare ciò che si desidera.

+0

Grazie per la soluzione. Sfortunatamente, sto usando readline ed è piuttosto importante. – wujek

+0

@wujek si, mi aspettavo tanto. Bene, c'è un modo leggermente più incisivo di gestire quello che immagino, lo aggiungerò. –

+0

Ho scelto l'opzione 3, ho appena copiato il codice cmdloop() e cambiato. Penso che sia la soluzione meno hacky ... Grazie! – wujek