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

时间:2012-01-11 02:07:44

标签: python

使用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异常(preloop等)无济于事。我在sigints上读过一些内容,但不太清楚这是怎么回事。

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

8 个答案:

答案 0 :(得分:13)

我目前正在使用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 )处理程序。

答案 1 :(得分:6)

您可以抓住KeyboardInterrupt加注cmd.Cmd.cmdloop(),而不是使用信号处理。您当然可以使用信号处理,但这不是必需的。

在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!

答案 2 :(得分:0)

回应this answer中的以下评论:

  

这似乎是在解决方案上趋同,但我不知道如何将它集成到我自己的代码中(上图)。我必须弄清楚'超级'线。我需要尝试在将来的某个时候使用它。

如果您的课程除了super()之外还有object,那么

cmd.Cmd将适用于此答案。像这样:

class Console(cmd.Cmd, object):

答案 3 :(得分:0)

我想做同样的事情,因此我进行了搜索并创建了基本的脚本以理解,我的代码可以在我的GitHub

上找到

所以,在您的代码中,

代替此

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

使用此,

if __name__ == '__main__':
    console = Console()
    try: 
       console.cmdloop()
    except KeyboardInterrupt:
       print ("print sth. ")
       raise SystemExit

希望这会对您有所帮助。好,对我有用。 ?

答案 4 :(得分:-1)

您可以使用CTRL-C signal抓住signal handler。如果你添加下面的代码,解释器拒绝退出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 """

答案 5 :(得分:-1)

您可以查看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)

答案 6 :(得分:-2)

只需在课程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”。奇怪的是,如果你SPAM ctrl + C你最终会看到它打印第二行通常只打印在ACTUAL出口(ctrl + Z然后输入,或输入退出)。

答案 7 :(得分:-2)

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

import signal

def handler(signum, frame):
    pass

signal.signal(signal.SIGINT, handler)