Create your own interactive shell with cmd in Python
When writing an Command Line Interface for an application it could be nice to have an interactive shell with command completition and history. The cmd library of Python provides a framework for that.
We will build an application step-by-step. Scroll down to the end of the page to see a full example with all the bells and whistles.
Skeleton of the Interactive shell
The recommendation is to subclass the Cmd class, so that's what we do here, though as being a skeleton we don't do anything else with it.
examples/python/cli/skeleton.py
from cmd import Cmd class MyPrompt(Cmd): pass MyPrompt().cmdloop()
We create an instance object of the MyPrompt class and immediately call the cmdloop method. We could have used a temporary variable there if we wanted to be a bit more verbose like this:
p = MyPrompt() p.cmdloop()
but the result is the same.
When we run this script it will display a default prompt:
(Cmd)
You can't do much with it. You can type in ? and press ENTER to get the following help:
Documented commands (type help <topic>): ======================================== help
help # (Cmd) Ctrl-C
You can ask for help on help by typing in:
help help
And it will print:
List available commands with "help" or detailed help with "help cmd".
If you would like to exit the application you need to press Ctlr-C and get a KeyboardInterrupt.
First commands
You can add commands to the system by implementing the corresponding do_* methods.
examples/python/cli/commands.py
from cmd import Cmd class MyPrompt(Cmd): def do_exit(self, inp): print("Bye") return True def do_add(self, inp): print("Adding '{}'".format(inp)) MyPrompt().cmdloop() print("after")
We implemented to commands: exit and add.
When we run the script it will show use the standard prompt:
(Cmd)
If at this point we press TAB twice we get the list of all the available command. In our case that is
add exit help
If we type in help we get the following output:
(Cmd) help Documented commands (type help <topic>): ======================================== help Undocumented commands: ====================== add exit
If we type in help add as the help window suggests for the documented commands we get:
(Cmd) help add *** No help on add
If we type in add followed by some text and press ENTER the system will run the do_add method and pass the text to the method. In our case we get:
(Cmd) add Hello World Adding 'Hello World'
If we type in exit it will call the do_exit method, print "Bye" and exit the cmdloop. In our case it means it will go on and print the string "after".
(Cmd) exit Bye after
Help - documenting commands
As you could see above, the built-in help command had some documentation, but the two command we added did not have any. There are two ways to add documentation to a command. Either by adding a method with the help_* prefix or by adding docstring to the appropriate do_* method.
examples/python/cli/help.py
from cmd import Cmd class MyPrompt(Cmd): def do_exit(self, inp): '''exit the application.''' print("Bye") return True def do_add(self, inp): print("Adding '{}'".format(inp)) def help_add(self): print("Add a new entry to the system.") MyPrompt().cmdloop()
Entering ? not all the commands are listed under the "Documented commands":
(Cmd) ? Documented commands (type help <topic>): ======================================== add exit help
We can get help by typing in help and the name of the command:
(Cmd) help add Add a new entry to the system.
(Cmd) help exit exit the application.
We can still exit the application:
(Cmd) exit Bye
Prompt and banner
The default prompt is (Cmd) but we can override it using the prompt attribute of the class.
In addition we can set a text to be the banner, that is the text shown when we launch the application, before the first prompt is shown. We only need to assign the text to the intro attribute.
examples/python/cli/prompt.py
from cmd import Cmd class MyPrompt(Cmd): prompt = 'pb> ' intro = "Welcome! Type ? to list commands" def do_exit(self, inp): '''exit the application.''' print("Bye") return True MyPrompt().cmdloop()
If we run this application we'll see:
Welcome! Type ? to list commands pb>
Default actions
Sometime you might want to be able to freely parse the input. For example if you'd like to implement short, one-letter alternatives of longer commands. If you implement a method called default that method will be called every time a command is entered that does not correspond to any of the do_* methods.
examples/python/cli/default.py
from cmd import Cmd class MyPrompt(Cmd): def do_exit(self, inp): '''exit the application. Shorthand: x q.''' print("Bye") return True def default(self, inp): if inp == 'x' or inp == 'q': return self.do_exit(inp) print("Default: {}".format(inp)) MyPrompt().cmdloop()
In this example we catch stand-alone x and q characters and call the do_exit method for them.
Ctrl-d EOF
You might have noticed thet if you press Ctrl-d, the standard way to exit most command line application, our examples print *** 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.
Full example
This examples includes all of the above techniques.
examples/python/cli/full.py
from cmd import Cmd class MyPrompt(Cmd): prompt = 'pb> ' intro = "Welcome! Type ? to list commands" def do_exit(self, inp): print("Bye") return True def help_exit(self): print('exit the application. Shorthand: x q Ctrl-D.') def do_add(self, inp): print("adding '{}'".format(inp)) def help_add(self): print("Add a new entry to the system.") def default(self, inp): if inp == 'x' or inp == 'q': return self.do_exit(inp) print("Default: {}".format(inp)) do_EOF = do_exit help_EOF = help_exit if __name__ == '__main__': MyPrompt().cmdloop()
See also the Cmd wiki page.
Published on 2018-02-15