Crafting command line outputs
Tags: cli
Published: 2019-03-30 21:47:08

---
title: Crafting command line outputs
published: true
tags: [cli]
canonical_url: https://dmerej.info/blog/post/crafting-command-line-outputs/
---


*Originally published on [my blog](https://dmerej.info/blog/post/crafting-command-line-outputs/)*

I’ve used many command line tools. It always makes me feel happy when I come across a tool that has a nice, usable and friendly output. I always try to make those I create or maintain as good as possible in this regard.

Along the way, I’ve learned a few tricks I’d like to share with you.

# Start with the contents

When designing command line output, start with the _contents_, not the style.In other words, think about _what_ to print before thinking about _how_ to print it - this is helpful in a lot of other situations.

# Error messages

The absolute minimum your command line tool needs are _good error messages_.Pay attention to those - good error messages can go a long way in helping your users and may even save you from having to answer a bunch of bug reports! [^1]

A good error message should:

- Start with a clear summary describing what went wrong
- Contain as many details as relevant (but not too much)
- And possibly a suggestion on how to fix the issues.

Bad example:

```
Could not open cfg file. ENOENT 2

```

Good example:

```
Error while reading config file (~/.config/foo.cfg)
Error was: No such file or directory
Please make sure the file exists and try again

```

# Standard out and standard error

Speaking of error messages, note that your can choose to write text to _two_ different channels (often called `stdout` and `stderr` for short). Use `stderr` for error messages and error messages only. People sometimes need to hide “normal” messages from your tool, but they’ll need to know about those errors!

# Colors

Things to know about colors:

- Use red for errors (that’s about the only convention I know of which is somewhat followed by everyone).
- You can get a lot of meaning out of simple, ASCII-art decoration. Don’t necessarily reach from emojis immediately 😜.
- Try to use colors in a _consistent_ way. Having helper methods like `print_error()`, `print_message ()` can help.
- On Linux and macOS, coloring is achieved by emitting certain non-printable ASCII characters (sometimes referred to as ANSI escape codes). This is fine when your program runs in a terminal, but _not_ when its output is redirected to a file, for instance.
- People usually expect color activation to be controlled with a tri-state: “always”, “never”, or “auto”. The first two are self-explanatory, but “auto” needs some explaining.
- When “auto” is set, your program should decide whether to use colors by itself. You can do so by calling `isatty(stdout)` or something equivalent.
- On Windows, coloring is achieved by using the win32 API, but the same ideas apply.
- There’s probably a library near you that implements all of this. Even if it does not seem much, there’s no point in re-inventing the wheel here.
- Finally, try to follow the [CLICOLORS standard](https://bixense.com/clicolors/).

# Progress

If your comman-line tool does some lengthy work, you should output _something_ to your users so that they know your program is not stuck.

The best possible progress indicator contains:

- An ETA
- A progress bar
- A speed indicator

`wget` does that really well. Your favorite language probably has one or two ready-to-use libraries for this.

If you can’t achieve the full progress bar, as a lightweight alternative you can just display a counter: (`1/10`, `2/10`, etc.). If you do so, please make sure the user knows _what is being counted_ !

# Remove noise

We already saw that people maybe running your tool without a terminal attached. In this case, you should skip displaying progress bars and the like.

Also, try to remove things that are only useful when debugging (or don’t display them by default).

This includes the time taken to perform a given action (unless it’s useful to the end-user, like if you are writing a test runner for instance). Also, don’t forget that users can and will prefix their command line with `time` to get precise results if they need to.

A good technique to remove noise is to completely erase the last line before writing a new one.

Here’s how to do it in Python:

```
size = shutil.get_terminal_size() # get the current size of the terminal
print(first_message, end="\r")
do_something()
print(" " * size.columns, end="\r") # fill up the line with blanks
                                     # so that lines don't overlap
print(second_message)
```

Of course, this only works if the user does not need to know about the whole suite of messages!

# Tests

End-to-end tests are a great way to tweak the output without having to do a bunch of setup by hand, _and_ check the error messages look good.

# Parting words

Well, that’s all I’ve got today. Please keep those tips in mind when creating your own command line tool, and if you find a program that does not adhere to these rules, feel free to send patches. Until next time!


[^1]: Assuming they take the time to _read_ the output. But the better they are, the more they’re likely to get read :)

---


_I'd love to hear what you have to say, so please feel free to leave a comment below, or check out [my contact page](https://dmerej.info/blog/pages/contact/) for more ways to get in touch with me._