[英]Handle CTRL-C in Python cmd module
我使用cmd模塊編寫了一個Python 3.5應用程序。 我想要實現的最后一件事是正確處理CTRL-C(sigint)信號。 我希望它的行為或多或少與Bash的行為方式相同:
基本上:
/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.