2016-05-22 20 views
5

Napisałem aplikację Python 3.5 za pomocą modułu cmd. Ostatnią rzeczą, którą chciałbym zaimplementować, jest właściwa obsługa sygnału CTRL-C (sigint). Chciałbym, aby zachowywać się mniej więcej sposób atakujących robi:Uchwyt CTRL-C w module cmd Python

  • drukuj^C w punkcie kursor znajduje
  • wyczyścić bufor, tak aby wprowadzić tekst zostanie usunięty
  • Przejście do następnego linia, wydrukować wiersz, i czekać na wejście

Zasadniczo:

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

Tutaj jest uproszczony kod jako runnable próbka:

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() 

To prawie działa. Kiedy naciśniesz CTRL-C,^C zostanie wydrukowane przy kursorze, ale nadal muszę nacisnąć enter. Następnie metoda precmd odnotowuje flagę self._interrupted ustawioną przez procedurę obsługi i zwraca pustą linię. To jest tak daleko, jak mogłem to wziąć, ale nie chciałbym w żaden sposób naciskać tego wejścia.

Chyba muszę jakoś zmusić do powrotu input(), czy ktoś ma pomysły?

+0

Pomoże to mieć małą próbkę, którą można uruchomić. Nie uruchamiając swojego kodu, w jaki sposób obecnie się o to rozprawiasz, wystarczy napisać ''\ n'' lub puste na' stdout'? To jest po tym, jak obsługa sygnału zostaje potknięta w kierunku znaku nowej linii na końcu ''^ C'' – tijko

+1

@tijko Edytował post, zamieniając fragment kodu na próbkę działającą. Próbowałem napisać znak nowej linii na standardowe wyjście (domyślnie jest to metoda drukowania), ale to nie działa i dlaczego? Wymagałoby to, aby stdin był podawany ze standardowego wyjścia, co nie ma miejsca, nie jest podłączony do instalacji rurowej. – wujek

+0

Tak, masz rację, nie myślałem w terminach linii poleceń STDIN. @DanGetz ma odpowiedź, która będzie pasować do twoich potrzeb. Jest także czystszy, nie trzeba ustawiać obsługi sygnału ani ustawiać flag. Po twoim komentarzu zacząłem myśleć wzdłuż linii przekierowania standardowego wejścia, możesz dostosować się do tego, czego potrzebujesz. – tijko

Odpowiedz

3

Znalazłem kilka hacky sposoby osiągnięcia pożądanego zachowania za pomocą Ctrl-C.

Set use_rawinput=False i zastąpić stdin

tej jednej laski (mniej lub bardziej ...) do publicznego interfejsu cmd.Cmd. Niestety wyłącza obsługę readline.

Możesz ustawić wartość use_rawinput na wartość false i przekazać inny obiekt podobny do pliku, aby zastąpić stdin w Cmd.__init__(). W praktyce na tym obiekcie wywoływane jest tylko readline(). Więc można utworzyć otoki dla stdin że łapie KeyboardInterrupt i wykonuje zachowań chcesz zamiast:

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() 

Gdy uruchomię to na moim terminalu, Ctrl-C pokazuje ^C i przełączniki do nowej linii.

Monkey-łata input()

Jeśli chcesz wyniki input(), z wyjątkiem chcesz różne zachowanie dla Ctrl-C, jednym ze sposobów, aby to zrobić byłoby użyć inną funkcję zamiast 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' 

Jednakże, jeśli ślepo ustawisz input = my_input, otrzymasz nieskończoną rekursję, ponieważ my_input() zadzwoni pod numer input(), który sam jest teraz.Ale to naprawić, można załatać słownika w module cmd aby wykorzystać swoją zmodyfikowaną metodę __builtins__input() podczas 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 

    # ... 

Należy zauważyć, że zmienia input() dla wszystkie Cmd obiekty, nie tylko TestShell obiektów. Jeśli to nie jest do zaakceptowania dla Ciebie, można ...

kopiowania źródła Cmd.cmdloop() i zmodyfikować go

Skoro jesteś instacji go, można dokonać cmdloop() rób co chcesz. "Wszystko, co chcesz" może obejmować kopiowanie części Cmd.cmdloop() i przepisywanie innych. Zamień połączenie na input() z połączeniem na inną funkcję lub złap i obsłuż tam KeyboardInterrupt w przepisanym cmdloop().

Jeśli obawiasz się zmiany implementacji w nowych wersjach Pythona, możesz skopiować cały moduł cmd do nowego modułu i zmienić to, co chcesz.

+0

Dzięki za rozwiązanie. Niestety używam readline i jest to bardzo ważne. – wujek

+0

@wujek tak, spodziewałem się tak bardzo. Sądzę, że jest nieco trudniejszy sposób na obsłużenie, dodam to. –

+0

Wziąłem opcję 3, właśnie skopiowałem kod cmdloop() i zmieniono. Myślę, że to najmniej hackowe rozwiązanie ... Dzięki! – wujek