簡體   English   中英

在Python cmd模塊中處理CTRL-C

[英]Handle CTRL-C in Python cmd module

我使用cmd模塊編寫了一個Python 3.5應用程序。 我想要實現的最后一件事是正確處理CTRL-C(sigint)信號。 我希望它的行為或多或少與Bash的行為方式相同:

  • 在光標所在的位置打印^ C.
  • 清除緩沖區以刪除輸入文本
  • 跳到下一行,打印提示,然后等待輸入

基本上:

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

以下是作為可運行示例的簡化代碼:

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

這幾乎可行。 當我按下CTRL-C時,在光標處打印^ C,但我仍然需要按回車鍵。 然后, precmd方法注意到處理程序設置的self._interrupted標志,並返回一個空行。 這是我可以接受的,但我想以某種方式不必按此輸入。

我想我不知何故需要強制input()返回,是否有人有想法?

我發現了一些使用Ctrl-C實現所需行為的hacky方法。

設置use_rawinput=False並替換stdin

這個(或多或少......)粘在cmd.Cmd的公共接口上。 不幸的是,它禁用了readline支持。

您可以將use_rawinput設置為false並傳遞一個不同的類文件對象來替換Cmd.__init__() stdin 實際上,只在此對象上調用readline() 因此,您可以為stdin創建一個包裝器,捕獲KeyboardInterrupt並執行您想要的行為:

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

當我在終端上運行它時,Ctrl-C顯示^C並切換到新行。

猴子補丁input()

如果你想要input()的結果,除了你想要Ctrl-C的不同行為,一種方法是使用不同的函數而不是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'

但是,如果您只是盲目地設置input = my_input ,則會得到無限遞歸,因為my_input()將調用input() ,現在它本身就是。 但這是可以修復的,你可以在cmd模塊中修補__builtins__字典,以便在Cmd.cmdloop()期間使用修改后的input()方法:

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

    # ...

請注意,這會更改所有Cmd對象的 input() ,而不僅僅是TestShell對象。 如果這是你不能接受的,你可以......

復制Cmd.cmdloop()源並進行修改

由於你是子類化它,你可以讓cmdloop()做你想做的任何事情。 “你想要的任何東西”可能包括復制Cmd.cmdloop()並重寫其他部分。 通過調用另一個函數替換對input()的調用,或者在重寫的cmdloop()捕獲並處理KeyboardInterrupt

如果您害怕使用新版本的Python更改底層實現,您可以將整個cmd模塊復制到一個新模塊中,並更改您想要的內容。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM