Most programs need to interact with the outside world. For personal programming projects, I have a lot of freedom to choose how it does that. I could write it as a web application! Or a mobile app! Or a physical interface with Raspberry Pi buttons! But for some projects, the interface is just boilerplate I need to get out of the way so I can work on the interesting part.

For these, I like using command line interfaces. First, CLIs are probably the easiest interface to code. But also a lot of legit programs use CLIs (git! docker!), so for some of my projects, a CLI isn’t the hacky prototype, but a reasonable final product. Being polished and easy to code is a great combination.

CLI doesn’t have to mean interactive prompts

When I started coding, I thought a command line interface meant an interactive prompt.

If I’m writing a program that tells me the weather for a city, I might need the city name and some options. The program could have a command line interface with an interactive prompt like

$ ./weather.py
Where?
> Seattle
Emoji? [Y/n]
> Y
It is cold, dark, and rainy. 🌧

I rarely code interactive prompts anymore. I think, uh, “regular” commands are easier to code and are often a better user experience.

Command line tools

If I did the weather application today, I’d probably have the interface be more like

$ weather Seattle --emoji
It is cold, dark, and rainy. 🌧

As the programmer, this takes less work to write than the interactive prompt. I can use Python’s argparse.

As the user, I like this because I can enter everything at once. If I make a mistake, I can go back into my history and fix the command. I can also start stringing command line tools together, like

$ weather --emoji `get_current_city`

Config files

There are a few other patterns that make command line tools even cooler.

Instead of typing Seattle --emoji each time, I could write the program to take in a config file that looks like

city: Seattle
emoji: true

And then add code to read from a config file. Then I can call

./weather -c my_configs.yaml

If I travel to another city, I just need to edit the config file. I can also share the configs across different computers, or put them under version control.

This is admittedly a bit more code since I need to parse and validate the config file. I have some boilerplate using JSON Schema to help me out here. But depending on the project, it could be useful.

Files and stdin

I could also build a command line tool that uses stdin. Maybe I’d have a file like

- city: Seattle
  date: 2016-01-01
- city: Portland
  date: 2016-02-01
- city: San Francisco
  date: 2016-03-01
  
cat test_file | ./weather -c configs.yaml
Seattle was cold and rainy 🌧
Portland was cold and rainy 🌧
San Francisco was not cold nor rainy 🌤

To modify the program input, I just need to open the YAML file and edit the line I want to change.

With stdin, it’s easy to glue programs together. If I have one program that prints JSON, and one program that takes JSON through stdin, I can string them together. I can also be hacktastical and use Google Spreadsheets to edit a file, export as CSV, and use that as input.

Like the config, this does require a bit more code. But parsing a machine-readable file format is pretty easy.

Final observations

Like shinier user interfaces, command line interfaces need some considerations for user experience. A cool thing about command line interfaces is that I get exposed to them a lot. I have many opportunities to see useful and less useful ways to design a command line interface.

See Also

  • Acknowledgments to Brad, for pointing out about how files are totally legit interface for programs a few years ago.