在 cmd.Cmd 命令行解释器中更好地处理 KeyboardInterrupt
Posted
技术标签:
【中文标题】在 cmd.Cmd 命令行解释器中更好地处理 KeyboardInterrupt【英文标题】:Better handling of KeyboardInterrupt in cmd.Cmd command line interpreter 【发布时间】:2012-02-07 10:56:32 【问题描述】:在使用 python 的 cmd.Cmd 创建自定义 CLI 时,如何告诉处理程序中止当前行并给我一个新提示?
这是一个最小的例子:
# console_min.py
# run: 'python console_min.py'
import cmd, signal
class Console(cmd.Cmd):
def __init__(self):
cmd.Cmd.__init__(self)
self.prompt = "[test] "
signal.signal(signal.SIGINT, handler)
def do_exit(self, args):
return -1
def do_EOF(self, args):
return self.do_exit(args)
def preloop(self):
cmd.Cmd.preloop(self)
self._hist = []
self._locals =
self._globals =
def postloop(self):
cmd.Cmd.postloop(self)
print "Exiting ..."
def precmd(self, line):
self._hist += [ line.strip() ]
return line
def postcmd(self, stop, line):
return stop
def emptyline(self):
return cmd.Cmd.emptyline(self)
def handler(self, signum, frame):
# self.emptyline() does not work here
# return cmd.Cmd.emptyline(self) does not work here
print "caught ctrl+c, press return to continue"
if __name__ == '__main__':
console = Console()
console.cmdloop()
非常感谢您的进一步帮助。
原始问题和更多详细信息: [目前以下建议已被整合到这个问题中——仍在寻找答案。已更新以修复错误。]
我已经尝试将处理程序移动到循环外的函数中,看看它是否更灵活,但似乎没有。
我正在使用 python 的cmd.Cmd module 创建我自己的命令行解释器来管理与某些软件的交互。我经常按 ctrl+c 期待流行的类似 shell 的行为,即返回一个新的提示而不对输入的任何命令进行操作。但是,它只是退出。我试图在代码中的各个点(预循环等)捕获 KeyboardInterrupt 异常,但无济于事。我在sigints 上读过一些文章,但不太清楚它如何适合这里。
更新:根据下面的建议,我尝试实现信号并且能够这样做,这样 ctrl+c 现在不会退出 CLI,但会打印消息。但是,我的新问题是我似乎无法告诉处理函数(见下文)在打印之外做很多事情。我希望 ctrl+c 基本上中止当前行并给我一个新的提示。
【问题讨论】:
【参考方案1】:我目前正在使用 Cmd 模块创建一个 Shell。我遇到了同样的问题,我找到了解决方案。
代码如下:
class Shell(Cmd, object)
...
def cmdloop(self, intro=None):
print(self.intro)
while True:
try:
super(Shell, self).cmdloop(intro="")
break
except KeyboardInterrupt:
print("^C")
...
现在您在 shell 中有一个正确的 KeyboardInterrupt(又名 CTRL-C)处理程序。
【讨论】:
这似乎正在收敛于一个解决方案,但我不知道如何将它集成到我自己的代码中(上图)。我必须弄清楚“超级”线。我需要在未来的某个时候尝试让它发挥作用。 在 Python 2.x 中,cmd.Cmd
是一个老式类,所以super
不起作用。见this question。
为什么一定要自己打电话给postloop()
?
或者自己打印intro
,就此而言
intro
每次调用cmdloop()
时都会打印:如果您不希望每次 Ctrl-C 都再次打印介绍消息,则必须自己控制打印。【参考方案2】:
您可以只捕获cmd.Cmd.cmdloop()
引发的KeyboardInterrupt
,而不是使用信号处理。您当然可以使用信号处理,但这不是必需的。
在 while 循环中运行对 cmdloop()
的调用,该循环会在出现 KeyboardInterrupt
异常时自行重新启动,但由于 EOF 而正常终止。
import cmd
import sys
class Console(cmd.Cmd):
def do_EOF(self,line):
return True
def do_foo(self,line):
print "In foo"
def do_bar(self,line):
print "In bar"
def cmdloop_with_keyboard_interrupt(self):
doQuit = False
while doQuit != True:
try:
self.cmdloop()
doQuit = True
except KeyboardInterrupt:
sys.stdout.write('\n')
console = Console()
console.cmdloop_with_keyboard_interrupt()
print 'Done!'
执行 CTRL-c 只会在新行上打印一个新提示。
(Cmd) help
Undocumented commands:
======================
EOF bar foo help
(Cmd) <----- ctrl-c pressed
(Cmd) <------ctrl-c pressed
(Cmd) ddasfjdfaslkdsafjkasdfjklsadfljk <---- ctrl-c pressed
(Cmd)
(Cmd) bar
In bar
(Cmd) ^DDone!
【讨论】:
这是正确的解决方案。如果KeyboardInterrupt
被捕获,它可以将self.intro
设置为None
。
可以将while doQuit
循环放在__init__()
的末尾,而不是创建cmdloop_with_keyboard_interrupt()
方法,然后控制台将在调用Console() 时运行。
在cmdloop()
成功完成后使用while True:
和break
而不是doQuit
会更简单一些。【参考方案3】:
您可以使用signal handler 捕获CTRL-C signal。如果你添加下面的代码,解释器拒绝在 CTRL-C 上退出:
import signal
def handler(signum, frame):
print 'Caught CTRL-C, press enter to continue'
signal.signal(signal.SIGINT, handler)
如果您不想在每次 CTRL-C 后按 ENTER,只需让处理程序什么都不做,这将捕获信号而没有任何效果:
def handler(signum, frame):
""" just do nothing """
【讨论】:
什么都不做可以在Python中使用pass
来表达。【参考方案4】:
回应this answer中的以下评论:
这似乎是一个解决方案,但我不知道如何将它集成到我自己的代码中(上图)。我必须弄清楚“超级”线。我需要在未来的某个时候尝试让它发挥作用。
如果除了cmd.Cmd
之外,您的课程还扩展object
,super()
将在此答案中起作用。像这样:
class Console(cmd.Cmd, object):
【讨论】:
【参考方案5】:我也想做同样的事情,所以我搜索了它并创建了基本脚本以供理解,我的代码可以在我的GitHub上找到
所以,在你的代码中,
而不是这个
if __name__ == '__main__':
console = Console()
console.cmdloop()
用这个,
if __name__ == '__main__':
console = Console()
try:
console.cmdloop()
except KeyboardInterrupt:
print ("print sth. ")
raise SystemExit
希望这对您有所帮助。好吧,它对我有用。 ?
【讨论】:
【参考方案6】:您可以查看signal
模块:http://docs.python.org/library/signal.html
import signal
oldSignal = None
def handler(signum, frame):
global oldSignal
if signum == 2:
print "ctrl+c!!!!"
else:
oldSignal()
oldSignal = signal.signal(signal.SIGINT, handler)
【讨论】:
【参考方案7】:只需在 Console(cmd.Cmd)
类中添加这个:
def cmdloop(self):
try:
cmd.Cmd.cmdloop(self)
except KeyboardInterrupt as e:
self.cmdloop()
忘记所有其他的东西。这行得通。它不会在循环内产生循环。当它捕获KeyboardInterrupt
时,它会调用do_EOF
,但只会执行第一行;因为您在do_EOF
中的第一行是返回do_exit
,这很好。do_exit
调用postloop
。
然而,同样,它只执行cmd.Cmd.postloop(self)
之后的第一行。在我的程序中,这是打印“\n”。奇怪的是,如果你是垃圾邮件 ctrl+C,你最终会看到它打印第二行,通常只在实际退出时打印(ctrl+Z 然后输入,或输入退出)。
【讨论】:
这是我最终采用的方法。唯一可能的问题是,如果“intro”是我上面示例中设置的属性,它将不断打印介绍(灵感来自下面的@seb 评论)。所以我希望可以编辑您的评论以反映这一点。 递归限制有人吗?如果两个KeyboardInterrupt
s 靠得太近也会有问题。【参考方案8】:
我更喜欢信号方法,但只是通过。并不真正关心在我的 shell 环境中提示用户任何内容。
import signal
def handler(signum, frame):
pass
signal.signal(signal.SIGINT, handler)
【讨论】:
这根本不符合 OP 的要求 - 即忽略当前在提示中写入的输入。以上是关于在 cmd.Cmd 命令行解释器中更好地处理 KeyboardInterrupt的主要内容,如果未能解决你的问题,请参考以下文章