CLI best practices
This article pulls together a number of tips and best practices for building CLI applications.
Input
- Use standard library input functions to read from stdin that can play nicely with rlwrap(1) and other input sources.
- If using readline functionality, provide an option to disable it.
- Only use readline functionality if you have used isatty(3) to test if stdin is a terminal.
- Don't pass sensitive information like passwords and API tokens on the command-line since they will appear in the output of ps(1). Instead store them in a file with appropriate ownership & permissions or pass them to the application in an environment variable.
-
If you need to interactively read sensitive information
such as passwords,
read directly from
/dev/ttyor use curses/ncurses calls to prevent sensitive input from displaying on the screen.
Output
- Though obvious, put errors on stderr and output on stdout.
- Only show progress meters if requested via a command-line option.
-
Allow tunable output verbosity,
preferrably including a completely silent mode
(often
-q/--quietor-s/--silent) and increasing levels of info/debugging output (often one or more-v/--verboseoptions). - Make color optional/smart by respecting $NO_COLOR if set, or $COLOR={always,never,auto}. If $COLOR (or $PROGNAME_COLOR) is not set, use isatty(3) to establish a default.
- Default output format should have one record per line with easy-to-discern/use column delimiters, not one record spanning multiple lines (unless requested as an output option like sqlite's ".mode line"). Use command-line options to specify other output formats such as JSON, XML, YAML, S-expressions, CSV or TSV.
- Don't produce binary output on stdout unless explicitly requested.
-
Print numbers unformatted
unless requested otherwise.
This might include internationalizing/localizing things like
currency markers,
thousands separators,
and decimal separators.
Or this might involve humanizing units,
often with a
-k(Kilobytes),-m(Megabytes),-g(Gigibytes), or-h/--humanizeflag. -
Offer formatting control for dates/time including
internationalizing/localizing
or explicitly specifying a format string.
If using a format string,
stick to one standard:
preferrably
strftime(3)
strings
(
%Y-%m-%d %H:%M), but CLDR format date strings, or PHP style date format strings are also common in certain communities.
Command-line options & switches
-
Provide both
short
(
-x) and long (--long-option) versions of options. On non-GNU systems, long options are less expected. -
Use common command-line option conventions:
-
-h/--helpshould give an example of usage, a description of what the program does, along with a list of options and their descriptions. Note that-hmay conflict with "human output". -
-v/--versionshould give version information. Note that-vmay conflict with verbosity flags. -
If a command has side-effets
such as deleting/renaming/modifying files
or transferring large volumes of data,
providea
-n/--dry-runto let the user see what will happen without actually performing the requested action. -
--to separate options and arguments. -
Use
-as a file-name to read from stdin. -
Use
@filenameto read data from filename instead of data provided directly on the command-line.
-
-
For long options that take arguments,
allow either an equals sign
or a space
to separate the option
from its arguments.
E.g.
--name=Samor--name Sam. - It can be beneficial to provide an option to expose expected completions for shell command-line completion functions.
Defaults
- If an option can have a default value, provide one.
- If output is truncated to the width of the tty, use curses calls to determine the width and fall back to 80 columns otherwise.
-
If spawning an external editor,
check the following environment variables and binaries in order
- $PROGNAME_EDITOR
- $VISUAL
- $EDITOR
- vi(1)
- ed(1)
Other considerations
- Where possible, allow the user to specify options in any order. Requiring options in a particular order makes things more fragile.
-
If one tool provides multiple actions,
use the form
"
programname {global options} action {action-specific options}". Examples include- git (
git commit) - Subversion (
svn commit) - Mercurial (
hg commit) - CVS (
cvs commit) - taskwarrior (
task list) - managing services (
service httpd start)
- git (
- Use $LC_CTYPE to determine the encoding of stdin/stdout but allow the user to specify alternate encodings and error/replacement strategies.
-
Return a 0 exit status on success.
Return a non-0 exit status
if any errors occurred,
preferrably using standard values from
/usr/include/sysexits.h. -
Store local user configuration in
~/.config/$PROGNAME/ -
Settings should be determined in the following order
(from lowest priority to highest precedence)
- built in configuration
-
system-wide configuration from either
/etc/${PROGNAME}rc,/etc/${PROGNAME}.conf,/etc/${PROGNAME}/*,/usr/local/etc/${PROGNAME}rcor/usr/local/etc/${PROGNAME}.conf -
user configuration file in
~/.config/$PROGNAME/* - environment variables
- command-line options
-
On Microsoft Windows,
these conventions may differ.
Notably, the use of
/option:valueinstead of--option=value