Your CLI-based program has to communicate with the user. The most obvious case is to display error or warning messages, but in some cases it is also to report progress status. There are a few details to be considered in this area.

Quietness vs. verbosity by default

You’ve probably heard the “No news is good news” principle. This is a major guideline behind the design of most standard Unix tools: when no problems arise, the tool just does what it was asked for and does not print anything on the screen. Only when something goes wrong an error or warning message is printed. Examples include cp, mv, git add, etc.

This quiet behavior is usually fine for “fast” applications—i.e. applications expected to complete in less than a second. The user is trained not to expect any message and the user knows that the appearance of a message on the console as a result of his invocation means that something failed. If your program prints a non-error message on the console and then immediately terminates successfully, the user will probably think that something went wrong and the application didn’t do what was expected. (Hint: users don’t read.)

Of course, it is perfectly fine to implement a --verbose or similar flag in these applications that explicitly requests the program to print more data. But it should not be the default.

The counterpart of these “quiet by default” applications are programs that can take a noticeable amount of time to complete — be it seconds, minutes or hours. Examples include git clone, rsync, wget, etc.

In the case of these “slow” applications, it is OK, and recommended, to report the progress of the operation on the screen. Otherwise the user won’t know why the program is not returning (especially in these days where computers have fewer and fewer status lights) and may be tempted to kill it.

Formatting

A common approach to format the status messages of CLI-based applications is to print the name of the program first, followed by a colon and a space, and then followed by the message. For example, for an error:

myprogram: Must provide at least one URL.

And for a warning:

myprogram: Failed to connect to server; skipping.

And for a regular progress message:

myprogram: Connecting to http://www.example.com/...

These are just fine and, in fact, this is the bundled style in the widespread err(3) family of functions. However, I’d personally recommend to toss in the severity of the message in the same line: in other words, specify whether a line is an error, a warning or just informational.

The first style you can use is to write the ERROR and WARNING words in error and warning messages respectively, and leave informational messages alone. For example:

myprogram: ERROR: Must provide at least one URL.
myprogram: WARNING: Failed to connect to server; skipping.
myprogram: Connecting to http://www.example.com/...

The second style you can use is to write a single letter indicating the severity of the message and add that letter to every single message printed by the program. For example:

myprogram: E: Must provide at least one URL.
myprogram: W: Failed to connect to server; skipping.
myprogram: I: Connecting to http://www.example.com/...

There of course several variations of the above, but not that many really. You should consider what other applications your program will play with and follow their style for consistency, if applicable.

Me? I personally prefer the second style because it preserves the orthogonality of the messages and it makes the severity of every line explicit.

Where to send the output?

As we learned from the Error reporting post, we know that error messages belong in stderr. But what about warning messages and informational messages? They do too!

So what goes into stdout? Only the actual output of the program. If you are expecting the program to print, say, the contents of a directory when you invoke it, then only the contents of the directory (and nothing else!) can go into stdout. Any debugging or auxiliary messages have to go to stderr.

It is probably easier to think about this guideline this way: if you were to implement a --verbose or --quiet option in your program, the specification of these options should have no effect on the contents of stdout. These options should only change the amount of logging that goes into stderr and/or into a separate log.


With this, we have covered the most important points of handling program output. There is one issue left though: what do you do with very long messages that do not fit on the screen? This is a large enough topic to deserve discussion in a separate post, so… stay tuned!