2016-05-22 19 views
5

Tôi đã viết một ứng dụng Python 3.5 bằng mô-đun cmd. Điều cuối cùng tôi muốn thực hiện là xử lý đúng đắn tín hiệu CTRL-C (sigint). Tôi muốn nó cư xử nhiều hay ít đường Bash làm nó:Xử lý CTRL-C trong mô-đun cmd Python

  • in^C tại điểm con trỏ là
  • rõ ràng bộ đệm để các văn bản đầu vào sẽ bị xóa
  • Skip to tiếp theo dòng, in dấu nhắc, và chờ cho đầu vào

về cơ bản:

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

đây được đơn giản hóa mã như một Runnable mẫu:

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

Điều này gần như hoạt động. Khi tôi nhấn CTRL-C,^C được in tại con trỏ, nhưng tôi vẫn phải nhấn enter. Sau đó, phương thức precmd thông báo cờ self._interrupted do trình xử lý thiết lập và trả về một dòng trống. Điều này là xa như tôi có thể mang nó, nhưng tôi muốn bằng cách nào đó không phải nhấn mà nhập.

Tôi đoán bằng cách nào đó tôi cần buộc input() trả lại, không ai có ý tưởng?

+0

Nó sẽ giúp có một mẫu runnable nhỏ. Nếu không chạy mã của bạn như thế nào bạn hiện đang đi về nó, sẽ viết một ''\ n'' hoặc trống để' stdout' đủ? Đó là một khi xử lý tín hiệu bị vấp phải một dòng mới vào cuối ''^ C'' – tijko

+1

@tijko Chỉnh sửa bài đăng bằng cách thay thế đoạn mã bằng một mẫu có thể chạy được. Tôi đã thử viết một dòng mới để stdout (nó thực sự được thực hiện theo mặc định bởi phương thức in), nhưng nó không hoạt động, và tại sao nó? Nó sẽ yêu cầu cho stdin được cho ăn từ stdout, mà không phải là trường hợp, nó không phải đường ống. – wujek

+0

Vâng, bạn nói đúng Tôi đã không suy nghĩ về dấu nhắc lệnh dòng lệnh stdin. @DanGetz có câu trả lời phù hợp với nhu cầu của bạn mà tôi tin. Nó sạch hơn, không cần phải thiết lập xử lý tín hiệu hoặc thiết lập cờ. Sau khi bình luận của bạn tôi bắt đầu suy nghĩ dọc theo các dòng chuyển hướng stdin là tốt, bạn có thể điều chỉnh để tuy nhiên bạn cần. – tijko

Trả lời

3

Tôi đã tìm thấy một số cách hacky để đạt được hành vi bạn muốn với Ctrl-C.

Set use_rawinput=False và thay thế stdin

này một gậy (nhiều hay ít ...) để giao diện công cộng của cmd.Cmd. Thật không may, nó vô hiệu hóa hỗ trợ readline.

Bạn có thể đặt use_rawinput thành sai và chuyển một đối tượng giống như tệp khác để thay thế stdin trong Cmd.__init__(). Trong thực tế, chỉ có readline() được gọi trên đối tượng này. Vì vậy, bạn có thể tạo một wrapper cho stdin mà bắt KeyboardInterrupt và thực hiện hành vi mà bạn muốn thay vì:

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

Khi tôi chạy trên thiết bị đầu cuối của tôi, Ctrl-C cho thấy ^C và chuyển sang một dòng mới.

Khỉ-vá input()

Nếu bạn muốn kết quả của input(), ngoại trừ bạn muốn hành vi khác nhau cho Ctrl-C, một trong những cách để làm điều đó sẽ được sử dụng một chức năng khác nhau thay vì 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' 

Tuy nhiên, nếu bạn chỉ nhắm mắt một cách mù quáng input = my_input, bạn sẽ nhận được đệ quy vô hạn vì my_input() sẽ gọi input(), hiện giờ là chính nó.Nhưng đó là fixable, và bạn có thể vá điển __builtins__ trong cmd module để sử dụng sửa đổi input() phương pháp của bạn trong 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 

    # ... 

Lưu ý rằng điều này thay đổi input() cho tất cả Cmd đối tượng, không chỉ TestShell đối tượng. Nếu đây là không thể chấp nhận được đối với bạn, bạn có thể ...

Sao chép mã nguồn Cmd.cmdloop() và sửa đổi nó

Vì bạn đang subclassing nó, bạn có thể làm cho cmdloop() làm gì bạn muốn. "Mọi thứ bạn muốn" có thể bao gồm việc sao chép các phần của Cmd.cmdloop() và viết lại những phần khác. Hoặc thay thế cuộc gọi đến input() bằng một cuộc gọi đến một chức năng khác hoặc nắm bắt và xử lý KeyboardInterrupt ngay tại số cmdloop() được viết lại của bạn.

Nếu bạn sợ việc triển khai cơ bản thay đổi với phiên bản Python mới, bạn có thể sao chép toàn bộ mô-đun cmd vào mô-đun mới và thay đổi những gì bạn muốn.

+0

Cảm ơn giải pháp của bạn. Thật không may, tôi đang sử dụng readline và nó là khá quan trọng. – wujek

+0

@wujek yeah, tôi mong đợi nhiều. Vâng, có một cách hơi khó khăn hơn để xử lý mà tôi đoán, tôi sẽ thêm nó. –

+0

Tôi đã chọn tùy chọn 3, chỉ cần sao chép mã cmdloop() và thay đổi. Tôi nghĩ rằng đó là giải pháp ít hacky nhất ... Cảm ơn! – wujek