3

I tend to talk about how simple command line applications are. Usually, I say something like, "they read from standard in and write to standard out and standard error, that's it." Then, I draw a picture like:

enter image description here

But then I started thinking that's not actually the end of the story. Command line applications read from standard in and write to standard error and standard out, and...

  • read configuration files (from any number of places like /etc, ~, or .)
  • read environment variables
  • read command line switches
  • read command line flags
  • read command line arguments
  • set the exit status

A command line application ends up looking more like:

enter image description here

Which is objectively more complex than simply stdin, stdout, and stderr.

I use command line applications all day. Over time I've developed an opinion and intuition about them:

  • standard in - usually data, not arguments (unless I use xargs)
  • standard out - the product, preferably columnated data
  • standard error - for logging cruft or error messages if something goes wrong
  • the exit status - sacred, either 0 for success or 1 for failure (or something else for some other type of failure)
  • arguments - important but read somehow differently than standard in
  • flags - like arguments but less important
  • switches - like flags but are either on or off
  • environment variables - sort of like arguments but there's some kind of philosophical difference between them
  • configuration files - kind of like environment variables in the sense that you can control switches or flags, but you can keep them in version control and generally use them to prevent having to use frustratingly combinations of switches and flags, or show everyone how cool your dotfiles are

These guidelines tend to work with various command line tools I use. However, when it comes to making them for other people to use, I'd like to have a reference.

For example, the following is a command line application written with Ruby:

#!/usr/bin/env ruby
# somecli

require 'optparse'
require 'yaml'

options = {}

etc_config = File.join('etc', 'somecli')
if File.exist? etc_config
  options.merge! YAML.load_file(etc_config)
end

home_config = File.join(ENV['HOME'], '.somecli')
if File.exist? home_config
  options.merge! YAML.load_file(home_config)
end

current_working_directory_config = '.somecli'
if File.exist? current_working_directory_config
  options.merge! YAML.load_file(current_working_directory_config)
end

OptionParser.new do |opts|
  opts.on("-s", "--[no-]switch") do |s|
    options[:switch] = s
  end
  opts.on("-a", "--[no-]another-switch") do |as|
    options[:'another-switch'] = as
  end
  opts.on("-y", "--[no-]yet-another-switch") do |yas|
    options[:'yet-another-switch'] = yas
  end
  opts.on("-y", "--[no-]even-yet-another-switch") do |eyas|
    options[:'even-yet-another-switch'] = eyas
  end
  opts.on("--flag FLAG") do |f|
    options[:flag] = f
  end
end.parse!

puts "ARGV=#{ARGV.inspect}"
puts "options=#{options.inspect}"
puts "ENV['cats']=#{ENV['cats'].inspect}"
unless STDIN.tty?
  puts "STDIN.read=#{STDIN.read.inspect}"
end
$stderr.puts "stderr: hello world!"
$stdout.puts "stdout: hello world!"
exit 0

When run it looks like

echo -n foo bar baz | ./somecli -s -f flap jacks; echo $?
ARGV=["jacks"]
options={:"another-switch"=>true, :"even-yet-another-switch"=>true, :switch=>true, :flag=>"flap"}
ENV['cats']="flapjacks"
STDIN.read="foo bar baz"
stderr: hello world!
stdout: hello world!
0

I'd like to have a link in the README to a more official document that guided it's input and output conventions and design decisions.

I know POSIX exists for deciding which common utility programs are installed on a system, except I'm wondering 2 things when it comes to building command line applications:

  1. Are there any other common ways for getting input into a command line program that I missed?
  2. Does authoritative documentation exist for conventions around how to organize the various inputs and outputs?
2

There are some general guidelines for command line utilities documented in POSIX.1/Single UNIX Specification.

See Section 12.2: Utility Syntax Guidelines.

1

I don't think there are any conventions but for common sense.

As for input and output methods, or in general, communication methods, there's one that you mentioned, but only partially. It's reading and writing to files. (You mentioned reading config files, or in general, file descriptors.)

Similar things to files are pipes and sockets. Eg. TCP, UDP or Unix sockets, which can be considered a way of Inter-process Communication (IPC). Other kinds of IPC include:

  • shared and mapped memory
  • signals
  • semaphores
  • D-Bus

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.