Stop Using Single-Letter Command Line Options

Single-letter command line options are inferior for documentation. Stop using them if you're doing anything that anyone else relies on.

Stop Using Single-Letter Command Line Options
Photo by Clint Patterson / Unsplash

What Am I Talking About?

These options go by many names. IEEE calls them “Utility Syntax”, GNU calls them “Command Line Options”, Python’s argparse just calls them options. I was originally going to refer to them as command line switches. Some people call them flags. Whatever they’re called, you know what I’m talking about. The bits of the program you pass in just before execution. Like:

grep --version

or maybe

objdump --file-headers <some file>

or even

docker run --volume $(pwd)/output:/output --interactive --tty <some package>

All of the programs are executed with long-named options. They were written explicitly so users could use general, intuitive naming to provide input to the program. It would be equally as valid to write the former commands with single-letter Unix-style options, as seen below. But before you look, I want you to forget you saw the above examples. Without looking, can you still tell me what these flags mean?

grep -V
objdump -f <some file>
docker run -v $(pwd)/output:/output -it <some package>

Not as easy, right? It gets even more complicated with more options passed in. I’ll pick on Docker because I dislike that their documentation uses single-letter options all over the place.

docker run -t -i --mount type=bind,src=/data,dst=/data -P -m 2G -c "1024" -u daemon busybox sh
$/

What is going on here? Unless you’re a Docker wizard, you’re going to have to look at the documentation to get even a cursory understanding. As much as I am ragging on Docker, they do provide the long-named options to alleviate this. We can write the same thing with:

docker run --tty --interaction --mount type=bind,src=/data,dst=/data --publish-all --memory 2G --cpu-shares "1024" --user daemon busybox sh
$/

Here, you can, in plain English, read that the intent is to run a container, opening up an interactive tty session. The container will mount a value. All of the container’s ports will be published to the host. We limit the cpu and memorylimits of the container. Finally, we set the user to daemon before running the container and starting the default shell. Even if you’ve only used Docker for a day, you get a general understanding without referring to the documentation. With the single-letter options, your eyes probably glazed over.

Why Are These Bad?

They’re not bad. Programmers are lazy; we want easy ways to do the things we do most often. I believe that good programs include both single-letter and long-named options. At the developer level, I believe it is poor form only to include single-letter options. At the individual user level, I believe it’s poor form to use single-letter options, when long name options exist, when you’re doing anything that other developers will engage with.

My specific problem with single-letter options are:

  1. They are difficult for novices of the program to understand and thus delay their engagement in meaningful work.
  2. They are objectively worse in situations where these commands are used in documentation and thus delay the reader from engaging in meaningful work; if I have to read a document to understand your documentation, there’s a problem.
  3. Single-named options are generally based on English equivalencies (ie -v is based on --version). Non-native English speakers now have to understand a second level of indirection to understand what’s going on (ie understand what version means and then appropriately apply that to -v).

Difficult to Understand

This covers points one and two. As someone who has mentored many junior developers, I have heard the phrase “what do tac X, Y, and Z” do?” in reference to understanding program options. Yes, I could point to the man page, but that’s yet another level of indirection. Users need fewer idiosyncrasies to remember, not more. GNU even thinks so.

Long-named styles allow users to apply what they know about one program to another. For instance, if I know that ls has an option named --directory, and what it does, I could use that information when I’m looking at someone else’s code snippet that uses the same option for du.

English Equivalencies

It’s harder for non-native English speakers. Don’t believe me on this one? What’s the most common single-letter option for recursive operations - for instance, if I wanted to perform an operation to every file within a directory? I’m guessing you’ll say -r or -R, because, well, recursion. Many programs use this: grep, ls, rm, etc. What about binwalk, a program that can extract files from within files (compressed or otherwise)? It’s -M, short for --matryoshka, like the nesting dolls. I get the intent, but I now need to understand what matryoshka nesting dolls are and then make the connection that -M is short for that word. I can only say that I am fortunate that binwalk allows me to use --matryoshka, otherwise, I would never have any idea what -M stood for.

You’ve Convinced Me, How Can I Implement?

Fortunately, most systems and languages can take care of this for you. In C, this is courtesy of GNU getopt_long. You can implement single and long-named options like this short example I adapted from Wikipedia:

#include <stdio.h>     /* for printf */
#include <stdlib.h>    /* for exit */
#include <getopt.h>    /* for getopt_long; POSIX standard getopt is in unistd.h */
int main (int argc, char **argv) {
    int c;
    static struct option long_options[] = {
    /*   NAME       ARGUMENT           FLAG  SHORTNAME */
        {"file",    required_argument, NULL, 'f'},
        {"verbose", no_argument,       NULL, 'v'},
        {"help",    no_argument,       NULL, 'h'},
        {NULL,      0,                 NULL, 0}
    };
    int option_index = 0;
    while ((c = getopt_long(argc, argv, "fvh",
                 long_options, &option_index)) != -1) {
        switch (c) {
        case 'f':
            printf ("This is how you could add a file.\\n");
            break;
        case 'v':
            printf ("You selected verbose mode!\\n");
            break;
        case 'h':
            printf ("Maybe a help menu?\\n");
            exit(0);
        default:
            printf ("?? getopt returned character code 0%o ??\\n", c);
        }
    }
    if (optind < argc) {
        printf ("non-option ARGV-elements: ");
        while (optind < argc) {
            printf ("%s ", argv[optind++]);
        }
        printf ("\\n");
    }
    exit (0);
}

Unfortunately, this is not super portable. Both macOS and FreeBSD only come with getopt and users must install the GNU version to use programs with getopt_long. Undoubtedly, this has something to do with the pervasiveness of single-letter options. However, it’s not a big leap to include this as a dependency, especially when using a package manager like homebrew or pkg.

Python has a number of argument and option parsing libraries. Using argparse is pretty standard and supports both types of options simultaneously.

>>> from argparse import ArgumentParser
>>> parser = ArgumentParser()
>>> parser.add_argument('--verbose', '-v', action='store_true')
_StoreTrueAction(option_strings=['--verbose', '-v'], dest='verbose', nargs=0, const=True, default=False, type=None, choices=None, required=False, help=None, metavar=None)
>>> parser.parse_args(['-v'])
Namespace(verbose=True)

Boom. Super easy. Ruby is similarly easy with the optparse gem (which is in the standard library). Ruby docs even provide this great minimal example.

require 'optparse'

options = {}
OptionParser.new do |opts|
  opts.banner = "Usage: example.rb [options]"

  opts.on("-v", "--[no-]verbose", "Run verbosely") do |v|
    options[:verbose] = v
  end
end.parse!

p options
p ARGV

Conclusion

Hopefully, this short article encourages you to use long-named options more frequently when working with others. There’s still a place for single-letter Unix options; maybe relegate them to one-off commands you’re doing yourself. Don’t commit documentation with them or use them in scripts others will end up using. It costs you little and saves others (or your future self) a lot of grief.

What do you think? If you agree or disagree, I would love to hear your thoughts!

Subscribe to Sean Deaton

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
jamie@example.com
Subscribe