关于python:在cmd.Cmd命令行解释器中更好地处理KeyboardInterrupt

Better handling of KeyboardInterrupt in cmd.Cmd command line interpreter

使用python的cmd.Cmd创建自定义CLI时,如何告诉处理程序中止当前行并给我一个新提示?

这是一个最小的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# 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模块创建自己的命令行解释器来管理与某些软件的交互。我经常按ctrl + c,期望返回新提示的流行的类似shell的行为,而不会对输入的任何命令起作用。然而,它只是退出。我试图在代码中的各个点捕获KeyboardInterrupt异常(preloop等)无济于事。我已经阅读了一些关于sigints的内容,但不太清楚这里是如何适应的。

更新:从下面的建议,我试图实现信号,并能够这样做,使ctrl + c现在不退出CLI,但打印消息。但是,我的新问题是我似乎无法告诉处理程序函数(见下文)在打印之外做很多事情。我想ctrl + c基本上中止当前行并给我一个新的提示。


我目前正在使用Cmd模块创建Shell。我遇到了同样的问题,我找到了解决方案。

这是代码:

1
2
3
4
5
6
7
8
9
10
11
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(aka CTRL-C)处理程序。


您可以只捕获cmd.Cmd.cmdloop()引发的KeyboardInterrupt,而不是使用信号处理。您当然可以使用信号处理,但这不是必需的。

在while循环中运行对cmdloop()的调用,该循环在KeyboardInterrupt异常上重新启动,但由于EOF而正常终止。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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('
'
)

console = Console()

console.cmdloop_with_keyboard_interrupt()

print 'Done!'

执行CTRL-c只会在新行上打印新提示。

1
2
3
4
5
6
7
8
9
10
11
12
13
(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!


我想做同样的事情,所以我搜索它并创建基本脚本以便理解,我的代码可以在我的GitHub上找到

所以,在你的代码中,

而不是这个

1
2
3
if __name__ == '__main__':
    console = Console()
    console.cmdloop()

用这个,

1
2
3
4
5
6
7
if __name__ == '__main__':
    console = Console()
    try:
       console.cmdloop()
    except KeyboardInterrupt:
       print ("print sth.")
       raise SystemExit

希望这会帮助你。好吧,它对我有用。 ??


回答此答案中的以下评论:

This appears to be converging on a solution, but I don't know how to integrate it into my own code (above). I have to figure out the 'super' line. I need to try and get this working at some point in the future.

如果除了cmd.Cmd之外你的类扩展objectsuper()将适用于此答案。像这样:

1
class Console(cmd.Cmd, object):

您可以查看signal模块:http://docs.python.org/library/signal.html

1
2
3
4
5
6
7
8
9
10
11
12
import signal

oldSignal = None

def handler(signum, frame):
    global oldSignal
    if signum == 2:
        print"ctrl+c!!!!"
    else:
        oldSignal()

oldSignal = signal.signal(signal.SIGINT, handler)

您可以使用信号处理程序捕获CTRL-C信号。如果你添加下面的代码,解释器拒绝退出CTRL-C:

1
2
3
4
5
6
import signal

def handler(signum, frame):
    print 'Caught CTRL-C, press enter to continue'

signal.signal(signal.SIGINT, handler)

如果您不希望在每个CTRL-C之后按ENTER键,只需让处理程序不执行任何操作,这将捕获信号而不会产生任何影响:

1
2
def handler(signum, frame):
   """ just do nothing"""


我更喜欢信号方法,但只是通过。不关心在shell环境中提示用户任何内容。

1
2
3
4
5
6
import signal

def handler(signum, frame):
    pass

signal.signal(signal.SIGINT, handler)


只需在类Console(cmd.Cmd)中添加:

1
2
3
4
5
    def cmdloop(self):
        try:
            cmd.Cmd.cmdloop(self)
        except KeyboardInterrupt as e:
            self.cmdloop()

忘记所有其他的东西。这有效。它不会在循环内部形成循环。当它捕获KeyboardInterrupt时它调用do_EOF但只执行第一行;因为do_EOF中的第一行是return do_exit,这很好。
do_exit调用postloop
但是,它再次只执行cmd.Cmd.postloop(self)之后的第一行。在我的程序中,这是打印" n"。奇怪的是,如果你SPAM ctrl + C你最终会看到它打印第二行通常只打印在ACTUAL出口(ctrl + Z然后输入,或输入退出)。