Writing Good Usage Strings

🚨🚨 BREAKING: TYPESCRIPT SNATCHES DEFEAT FROM THE JAWS OF VICTORY 🚨🚨

Lane Wagner

NOTE: If you aren’t interested in the commentary and just want to learn about the correct syntax for usage strings, you can skip directly to ‘Usage Syntax’.


To anyone who uses the command line on a UNIX-like system, usage strings are an absolutely essential feature to any tool you use that you probably never think too much about. In a simple and concise syntax they’re able to tell the user what options a tool accepts, what combinations of those options are legal, what arguments are optional, and so on.

Despite how widespread they are and how useful they are, a huge amount of developers still do not know how to write proper usage strings, even in manual pages, where they are most important. It is for this reason that I am writing this post, to hopefully teach more people how to write these strings properly.

On top of that, those aforementioned developers often don’t understand the actual purpose of the usage string. The purpose of the usage string is to remind the user of the software, what the option they mistyped was supposed to be, or if it takes an argument they forgot to pass. The usage string is not the comprehensive documentation of the interface of your program – that is the job of the manual page. Documentation goes in manual pages, something that an increasing number of projects are forgetting.

What You Shouldn’t Do

First, let’s take a look at an example of a bad usage string, and explore why it’s bad:

$ average-gnu-command
USAGE: average-gnu-command <OPTIONS> [FILES...]
OPTIONS:
	--help                    Print help.
	--shuffle=[={SEED|random|reverse|none}]
	                          Perform shuffle of prerequisites and goals.
	-1 STRING, --my-fake-flag-1=STRING
	                          Another super long description.
	# 50 other options omitted...

So the first but also the least important problem here is the capitalization. This obviously does not affect the usability at all so it’s mostly a non-issue, but SCREAMING AT THE USER LIKE THIS is going to look sloppy to a number of users. Almost all vendors of system utilities (the BSDs, MacOS, Busybox, Util-Linux, etc.) will write their usage strings in lowercase. The main exception here seems to be the GNU coreutils. Again though, this doesn’t matter too much and is mostly a matter of style.

Now let’s get into some proper usability issues!

Too Much Output

In the example above, average-gnu-command decided it was going to spit out documentation for over 50 different options to us out on to the terminal. This might sound awesome, but they just gave us almost an entire manual pages worth of content in a terminal that might be running in a split window on a laptop. This is not helpful, because if the option I wanted was actually there, there is a pretty good chance it did not even fit on my screen, and I now need to scroll my terminal trying to find what I want.

So now either I need to scroll, or I can re-run the command with the output piped through a pager1:

$ average-gnu-command 2>&1 | less

This still sucks; scrolling through a bunch of monospace, grey text is not very easy for human eyes to parse. Especially if you already suffer from some form of visual impairment. You know what does have syntax highlighting, consistent easy-to-read formatting, and paging abilities? The man(1) command2 – please just write actual descriptive manual pages for your tools.

Let me repeat that one more time.

WRITE ACTUAL MANUAL PAGES FOR YOUR TOOLS!

Preferably in mdoc(7), but man(7) or even scdoc(5) is better than no manual.

Like seriously, what the fuck is this? This is the output of ls --help on my desktop monitor that is vertically maximized – not even a laptop! More than half of the options are not even visible.

My terminal being flooded with text

Mutual Exclusion

Let me ask you a question: of all those flags we saw above, which ones can I use together? Which ones can I not use together? Don’t know? Yeah, me neither. The reason we don’t know is because the usage string thought that all we needed to know was that average-gnu-command accepted <OPTIONS>… thanks Captain Obvious!

Required Options

Now let me ask another question: which flags are required? Once again, we don’t know, because the author of the software we want to use didn’t bother to tell us.

What You Should Do

Now that we know about some of the usability problems associated with the usage strings of many command-line tools that exist today, let’s look at two good examples of high-quality usage strings, and how they concisely communicate important information to us. I came across both of these examples while working on my new OpenBSD server.

$ wc -x
wc: unknown option -- x
usage: wc [-c | -m] [-hlw] [file ...]
$ sed -x
sed: unknown option -- x
usage: sed [-aEnru] [-i[extension]] command [file ...]
       sed [-aEnru] [-e command] [-f command_file] [-i[extension]] [file ...]

Let’s focus on the wc(1) command first. Based on usage string that is only one line long, we can determine the follow facts about the usage of the program:

  • It accepts 5 different flags.
  • The -c and -m flags are mutually exclusive.
  • It accepts 0 or more file arguments after any given flags.

Notice how there is no noisy help documentation printed which describes the behaviour of each option flag. This is redundant and unnecessary, because these flags are all already documented in the extensive and high-quality wc(1) manual.

The usage string for sed(1) is also helpful. By just looking at it for less than a second we can determine facts such as:

  • The argument to -i is optional.
  • The positional command argument is mutually exclusive with the -e and -f flags.

Another thing to pay attention to is how in the sed(1) example, we actually see two usage lines. When using vertical bars to represent mutually-exclusive alternatives is too noisy or not practical we can always just use extra usage lines.

Now by this point the average Hacker News reader will exclaim something along the lines of ‘This is terrible! How can I know what -E even does?’. Well dear reader, you know by reading the manual page. Again… usage strings are not a substitute for actual documentation, they are nothing more than a quick reminder to someone who already knows how to use the software, on why what they tried to do was wrong.

So now that we’ve seen an example of good usage strings, let’s go more in-depth on the specific syntax.

Usage Syntax

NOTE: There is no stict standard specifying a specific syntax. The rules documented below are the general consensus amongst the OS utility vendors of various UNIX-like systems, and POSIX. When there is disagreement on a syntactical detail, the alternatives will be listed.


The first line of a usage string begins with the string ‘usage:’ followed by a space. Subsequent usage strings are aligned using spaces. When the usage line gets too long, continue on the next line space aligned to the options from the previous line.

NOTE: On the various BSDs and MacOS, ‘usage’ is typically written in all lowercase while on Linux ‘Usage’ with a capital ‘U’ is preferred by GNU, Util-Linux, and Busybox. Both variants are used by POSIX in their manual pages.

usage: foo [-abc] [--a-long-option] [--another-long-option]
           [--final-option] directory file
       foo [-de]

Anything optional should be wrapped in brackets. This includes options, arguments, arguments to options, or even parts of arguments.

usage: foo [-abc] [-i[optional]] [--long-option[=optional]]
           ftp://[user:password@]host[:port]/file[/] ...

Short options are grouped together and prefixed with a single ‘-’. Long options, and short options that accept arguments are listed individually. The following sorting rules apply:

  1. Short options that don’t accept arguments come first.
  2. Short options that accept arguments come second.
  3. Long options come third.
  4. Required options come fourth.
  5. Positional arguments come last.
  6. Options are sorted with numbers coming first, and capital letters coming before their lowercase variant.

NOTE: As an exception to rule 4, when the required option denotes the primary mode of the program, or significantly alters the program’s behaviour, it is generally written at the beginning such as with ssh-keygen(1).

usage: foo [-0abcDdz] [-e foo] [-f bar] [--bar] -x foo bar

Required short option arguments are separated by a space, optional short option arguments are not separated by a space, and long option arguments are separated by an equals sign.

usage: foo [-e foo] [-i[bar]] [--long-1=foo] [--long-2[=bar]]

Mutually exclusive options are grouped together, and separated by a space-padded bar. When an option’s argument must be one of a series of options, the different alternatives are separated by a bar and no spaces.

NOTE: To avoid an overly complex usage string, mutually exclusive options are usually denoted by writing multiple usage strings.

NOTE: Sometimes to avoid repetition or with more complicated alternatives, braces are used, such as in FreeBSD’s crontab(1). This is however, quite rare.

usage: foo [-a | -b] [-T ascii|html|pdf] [--color=always|auto|never]

If an argument can be passed one or more times it should be suffixed with a space and an ellipsis. If the argument is accepted zero or more times, wrap it in brackets.

usage: foo [-ab] [file ...]
       foo -c file ...