Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Better handling of KeyboardInterrupt in cmd.Cmd command line interpreter

Tags:

python

While using python's cmd.Cmd to create a custom CLI, how do I tell the handler to abort the current line and give me a new prompt?

Here is a minimal example:

# 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()

Further help is greatly appreciated.

Original Question and more details: [Currently the suggestions below have been integrated into this question -- still searching for an answer. Updated to fix an error.]

I've since tried moving the handler to a function outside the loop to see if it were more flexible, but it does not appear to be.

I am using python's cmd.Cmd module to create my own command line interpreter to manage interaction with some software. I often press ctrl+c expecting popular shell-like behavior of returning a new prompt without acting on whatever command was typed. However, it just exits. I've tried to catch KeyboardInterrupt exceptions at various points in the code (preloop, etc.) to no avail. I've read a bit on sigints but don't quite know how that fits in here.

UPDATE: From suggestions below, I tried to implement signal and was able to do so such that ctrl+c now doesn't exit the CLI but does print the message. However, my new issue is that I cannot seem to tell the handler function (see below) to do much beside print. I would like ctrl+c to basically abort the current line and give me a new prompt.

like image 404
hodgkin-huxley Avatar asked Jan 11 '12 02:01

hodgkin-huxley


People also ask

How do you raise KeyboardInterrupt?

In Python, there is no special syntax for the KeyboardInterrupt exception; it is handled in the usual try and except block. The code that potentially causes the problem is written inside the try block, and the 'raise' keyword is used to raise the exception, or the python interpreter raises it automatically.

What is except KeyboardInterrupt?

Keyboard Interrupt ErrorThe KeyboardInterrupt exception is raised when you try to stop a running program by pressing ctrl+c or ctrl+z in a command line or interrupting the kernel in Jupyter Notebook.

How do you press Ctrl C in python?

In order to carry out this action, we need to first press the ctrl key downwards and simultaneously press C key. These two steps can be automated by key_down() method and can only be used along with Shift, Alt and Control keys.


2 Answers

I'm currently working on a creation of a Shell by using the Cmd module. I've been confronted with the same issue, and I found a solution.

Here is the code:

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")
...

Now you have a proper KeyboardInterrupt (aka CTRL-C) handler within the shell.

like image 97
Seb Avatar answered Oct 31 '22 23:10

Seb


Instead of using signal handling you could just catch the KeyboardInterrupt that cmd.Cmd.cmdloop() raises. You can certainly use signal handling but it isn't required.

Run the call to cmdloop() in a while loop that restarts itself on an KeyboardInterrupt exception but terminates properly due to 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!'

Doing a CTRL-c just prints a new prompt on a new line.

(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!
like image 43
Joel Avatar answered Oct 31 '22 22:10

Joel