The Python cmd module provides a ready-made framework for line-oriented command interpreters. Instead of writing a manual input() loop, extracting command names, dispatching functions, and building help, you subclass cmd.Cmd and define methods whose names begin with do_.
This model is useful for administrative consoles, test harnesses, prototypes, simulators, database tools, and local interfaces for services. It does not replace a conventional argument-based CLI or a graphical application, but it gives users a persistent interactive session with prompts, history, and completion when readline is available.
Your first cmd.Cmd console
import cmd
class Console(cmd.Cmd):
intro = 'Console started. Type help or ?.'
prompt = '(app) '
def do_status(self, arg):
'Show the current status: status'
print('System operational')
def do_exit(self, arg):
'Close the console: exit'
print('Goodbye')
return True
if __name__ == '__main__':
Console().cmdloop()The text after do_ becomes the command name. A command method’s return value is passed through postcmd(); when the final value is true, cmdloop() stops.
How command dispatch works
For each line, the class treats the first prefix as the command name and passes the remainder as one string. Therefore, export customers.csv --compact invokes do_export() with customers.csv --compact.
def do_echo(self, arg):
self.stdout.write(arg + '\n')The framework does not parse complex options automatically. Use shlex.split() to preserve quoted values or embed an argparse parser in each command. The guide to Python shlex explains safe tokenization.
Built-in help
Every subclass inherits the help command. A docstring on do_status() becomes the default content for help status. A separate help_status() method can provide longer documentation.
def do_backup(self, arg):
'Create a backup: backup DESTINATION'
...
def help_backup(self):
self.stdout.write('Usage: backup DESTINATION\n')
self.stdout.write('Copies data to an approved folder.\n')Without an argument, help lists documented, undocumented, and miscellaneous topics. Customize the section headings with doc_header, undoc_header, and misc_header.
Command and argument completion
When readline is available, command names can be completed automatically. Add complete_name() for command-specific arguments.
ENVIRONMENTS = ['dev', 'staging', 'production']
def complete_environment(self, text, line, begidx, endidx):
return [name for name in ENVIRONMENTS if name.startswith(text)]
def do_environment(self, arg):
if arg not in ENVIRONMENTS:
self.stdout.write('Invalid environment\n')
return
self.environment = argThe line and index parameters let you vary suggestions by position. Never expose secrets, restricted file names, or objects the current user is not allowed to inspect.
Validate every argument
Input from the prompt is untrusted. Split it safely, validate counts, types, ranges, paths, and authorization. Never forward the text directly to eval(), exec(), or an operating-system shell.
import shlex
class Console(cmd.Cmd):
def do_user(self, arg):
try:
tokens = shlex.split(arg)
except ValueError as exc:
self.stdout.write(f'Invalid input: {exc}\n')
return
if len(tokens) != 1:
self.stdout.write('Usage: user NAME\n')
return
name = tokens[0]
if not name.isidentifier():
self.stdout.write('Invalid name\n')
return
select_user(name)An allowlist of accepted actions and values is safer than attempting to remove dangerous characters.
Unknown commands with default()
If no do_* method matches, default() runs. Use it for a friendly error or a suggestion.
import difflib
COMMANDS = ['status', 'backup', 'user', 'exit']
def default(self, line):
name = line.split(maxsplit=1)[0]
matches = difflib.get_close_matches(name, COMMANDS, n=1)
if matches:
self.stdout.write(f'Unknown command. Did you mean {matches[0]}?\n')
else:
self.stdout.write('Unknown command. Type help.\n')Do not use default() to execute arbitrary system commands. That turns a restricted tool into an open shell.
Empty-line behavior
By default, emptyline() repeats the last non-empty command. This can be dangerous for destructive actions such as deletion, billing, or deployment. Override it to do nothing.
def emptyline(self):
passYou could selectively repeat commands known to be idempotent, but the rule must be explicit and tested.
precmd() and postcmd()
precmd() receives the line before dispatch and may normalize, audit, or reject it. postcmd() runs after the command and can change whether the loop ends.
def precmd(self, line):
normalized = line.strip()
record_attempt(self.user, normalized)
return normalized
def postcmd(self, stop, line):
record_result(self.user, line, stop)
return stopDo not log passwords, tokens, or sensitive arguments. Use secure input methods when credentials are required.
preloop() and postloop()
preloop() runs once before the first prompt. It is suitable for opening a connection, loading configuration, or checking permissions. postloop() runs before returning and should release resources.
def preloop(self):
self.connection = connect()
def postloop(self):
self.connection.close()Use context managers or ExitStack for multiple resources, and handle interruptions so Ctrl+C does not leave locks or transactions open.
Execute one line with onecmd()
onecmd() interprets a string as if a user typed it. This is useful in tests, integrations, and controlled scripting.
console = Console()
result = console.onecmd('status')Usually you should not override onecmd(). The pre- and post-command hooks provide safer extension points.
Queue commands with cmdqueue
cmdqueue contains lines processed before reading new input. It supports startup scripts, macros, and replay.
console = Console()
console.cmdqueue.extend([
'status',
'environment staging',
'exit',
])
console.cmdloop()When commands come from a file, limit file size and command count, validate its path, and treat content as untrusted. For line-oriented file processing, see Python fileinput.
Custom input and output streams
The constructor accepts stdin and stdout. To use a supplied input stream, set use_rawinput=False.
from io import StringIO
source = StringIO('status\nexit\n')
target = StringIO()
console = Console(stdin=source, stdout=target)
console.use_rawinput = False
console.cmdloop()
print(target.getvalue())Write to self.stdout instead of using global print() when consistent redirection matters.
EOF and interruptions
End-of-file is passed as the special command EOF. Implement do_EOF() so redirected input and Ctrl+D close the session cleanly.
def do_EOF(self, arg):
self.stdout.write('\nClosing\n')
return TrueHandle KeyboardInterrupt at an appropriate boundary. Ensure cleanup still occurs and distinguish cancellation from a failed command.
Authorization per command
Administrative consoles require authorization, not only authentication. Check the user’s role for each critical operation.
def do_clear_cache(self, arg):
if 'admin' not in self.permissions:
self.stdout.write('Access denied\n')
return
confirm_and_clear_cache()Hiding a command from help is not access control. Users can still type the command name.
Test the console with memory streams
Test valid commands, unknown commands, incomplete arguments, EOF, empty lines, exceptions, and permissions.
def test_status():
source = StringIO('status\nEOF\n')
target = StringIO()
console = Console(stdin=source, stdout=target)
console.use_rawinput = False
console.cmdloop()
assert 'operational' in target.getvalue()Inject fake services instead of connecting to real databases or networks in unit tests.
Common mistakes
- Executing arguments with
shell=True. - Letting empty lines repeat destructive commands.
- Forgetting
do_EOF(). - Writing to global stdout instead of
self.stdout. - Exposing secrets through completion.
- Confusing hidden commands with authorized commands.
- Loading unlimited command files.
Best practices
- Keep each
do_*method small. - Delegate business logic to services.
- Validate arguments explicitly.
- Use consistent help and examples.
- Audit without sensitive data.
- Test with in-memory streams.
- Release resources in
postloop().
Conclusion
Python cmd removes much of the boilerplate involved in interactive consoles. It provides dispatch, help, history, completion, lifecycle hooks, streams, and command queues.
The security model is yours to design: validate data, authorize every action, avoid shells, and distrust command files. Consult the official cmd documentation and the readline documentation.







