【问题标题】:Better handling of KeyboardInterrupt in cmd.Cmd command line interpreter在 cmd.Cmd 命令行解释器中更好地处理 KeyboardInterrupt
【发布时间】: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 基本上中止当前行并给我一个新的提示。

【问题讨论】:

    标签: python


    【解决方案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 之外,您的课程还扩展objectsuper() 将在此答案中起作用。像这样:

    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 评论)。所以我希望可以编辑您的评论以反映这一点。
          • 递归限制有人吗?如果两个KeyboardInterrupts 靠得太近也会有问题。
          【解决方案8】:

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

          import signal
          
          def handler(signum, frame):
              pass
          
          signal.signal(signal.SIGINT, handler)
          

          【讨论】:

          • 这根本不符合 OP 的要求 - 即忽略当前在提示中写入的输入。
          猜你喜欢
          • 2012-09-26
          • 2019-05-21
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2023-01-11
          • 2016-06-21
          • 2011-10-27
          • 1970-01-01
          相关资源
          最近更新 更多