Create your custom interactive shell with python 🐍

It can happen that, sometimes, you need a CLI for you application, and maybe could be nice to have a custom interactive shell with command completion and history. Python has the Cmd class within the cmd module that provides a simple framework for writing line-oriented command interpreters.

The skeleton of the shell

from cmd import Cmd

class MyPrompt(Cmd):
    pass

MyPrompt().cmdloop()

The Cmd.cmdloop() repeatedly issue a prompt, accept input, parse an initial prefix off the received input, and dispatch to action methods, passing them the remainder of the line as argument.

Basically when we run the script above it will display a default prompt:

(Cmd)
The ``Cmd`` includes the ``help`` or ``?`` command to get your interactive shell help:

(Cmd) help

Documented commands (type help <topic>):
========================================
help

(Cmd) ?

Documented commands (type help <topic>):
========================================
help

(Cmd)

If you would like to exit the application you need to press ``Ctlr-C`` and get a ``KeyboardInterrupt``.

Prompt and Intro

The default prompt text is (Cmd) but it can be overridden using the prompt attribute of the class.

Furthermore we can set a text to be the banner (or the welcome message for example), that is the text message shown when we launch our application.

from cmd import Cmd

class MyPrompt(Cmd):
    prompt = "myshell>"
    intro = "Welcome!! Type ? or help for the commands list."
    pass

MyPrompt().cmdloop()

Complete example

Now I’d like to show you a complete example of a custom interactive shell. Imagine you wanna create a command to compute a sum of bunch of numbers (e.g. 1,4,5,78,23).

I’m gonna create a do_sum method that will compute the sum of the numbers.

from cmd import Cmd


class MyPrompt(Cmd):
    prompt = "myshell> "
    intro = "Welcome to MY shell! Type ? to list commands"

    def onecmd(self, line):
        try:
            return super().onecmd(line)
        except Exception as e:
            print(f"{e}")
            return False  # don't stop

    def do_exit(self, inp):
        print("Bye")
        return True

    def help_exit(self):
        print("exit the application. Shorthand: x q Ctrl-D.")

    def default(self, inp):
        if inp == "q":
            return self.do_exit(inp)

        print("Default: {}".format(inp))

    def help_sum(self):
        print("Run a sum")

    def do_sum(self, input):
        lst_input = [int(n) for n in input.split(",")]
        print(f"The sum is: {sum(lst_input)}")

    do_EOF = do_exit
    help_EOF = help_exit


if __name__ == "__main__":
    MyPrompt().cmdloop()

and see it in action:

~ python -m shell
Welcome to MY shell! Type ? to list commands
myshell> sum 1,2,3,4,5
The sum is: 15
myshell> sum "a,dsfg"
invalid literal for int() with base 10: '"a'

As you can see the first command return a valid and meaningful output. The second one fails due to the wrong output and it returns the error trace of the python interpreter. That’s nice huh? 😎

Taking a look at the code snippet above you can notice the onecmd method within the MyPromopt class.

    def onecmd(self, line):
        try:
            return super().onecmd(line)
        except Exception as e:
            print(f"{e}")
            return False  # don't stop

That method, that extends the one of the super class, is called when the user enters a command, and in case of an exception print the exception. This is a very useful feature to have when you are developing a command line application because in case of an error whatsoever, it won’t crash the application, and you can decide you custom fallback action.

The other method default is called every time a command is entered and it does not correspond to any of the do_* methods.

What about the do_EOF and help_EOF? You might have noticed that Ctrl-d, prints *** Unknown syntax: EOF. That’s because Ctrl-d send an EOF (End Of File) signal and by default Cmd does not know what to do with it.

The solution is to implement the do_EOF method that will be called when the user presses Ctl-d. As we already have a do_exit method, we can just assign that to the do_EOF and have both do the same. In order to provide help for the EOF, we can include a function called help_EOF that is assigned the help_exit function.

IMHO this is a great built-in feature, very useful when you wanna create a command line application as a public interface of you library.

On top of the cmd module a lot of 3rd party apps have been created. I’d like to mention cmd2 that can be defined a custom cmd module with steroids 😀.

I hope you enjoied this article! See you at next episode 👋