When writing a command line utility used by others – end users, colleague developers, etc. – one has often to make a compromise between two goals. One the one hand, the tool should guide the end user through the process to facilitate the actual task. This is often achieved with interactive dialogs. On the other hand, the edit-compile-run cycle should be as short as possible to speed up development. By definition, the tool should not be interactive to not let the programmer answer the same questions again and again and slow down development.

In the following, I want to outline a “design pattern” which can reconcile both requirements. Although it always seemed somewhat obvious to me, I haven’t seen in a lot in the wild: My proposed solution is to have a command line argument for every interactive dialog defining some input parameter.

Let me first show an example to get our minds on track and then give further advice on the implementation of such a system.

#!/usr/bin/perl

use strict;
use warnings;
use Getopt::Long;

my %opts = (); # save all cmd line opions in this hash
GetOptions ( # read cmd line args
    \%opts,
    "name=s", # provide name on cmd line with '--name "Tom"'
    "age=i", # provide age on cmd line with '--age 42'
);

# ask user or get values from cmd line
my $name = askName(\%opts);
$name = askName(\%opts); # prove that the same question is not asked twice
my $age = askAge(\%opts);

# "main"
print "Hi $name!\n";
print "You are $age years old.\n";
exit(0)


sub ask {
    # helper function to either access cmd line parameters or ask user if
    # needed
    my ($opts, $key, $prompt, $default) = @_;
    my $value = $opts->{$key};
    if (!defined($value)) { # not specified on cmd line
        $default = "" if (!defined($default)); # not default value provided
        print "$prompt [default: $default]:\n";
        $value = <STDIN>; # read user input
        chomp($value); # trim
        if ($value eq "") {
            $value = $default; # overwrite with default
        }
        $opts->{$key} = $value; # store provided value for future reference
    } else {
        # provided on cmd line
    }
    return $value;
}

sub askName {
    my ($opts) = @_;
    return ask($opts, "name", "Please enter your name", "Tom");
}

sub askAge {
    my ($opts) = @_;
    return ask($opts, "age", "Please enter your age", "42");
}

(Download)

The above example is in Perl but you can obviously apply this pattern in any language. The core concept is implemented in the ask() function. It checks whether a certain command line argument (the key) has been provided and just returns its value if that is the case. Otherwise, it will ask the user to input the specific parameter and store the value in the opts hash before returning it. The sample functions askName() and askAge make use of ask() by providing the expected command line argument name, a question to be asked and a default value. This setup allows to provide all, none or some arguments on the command line. Every missing bit will be asked. Calling askName() twice will not cause the question to be asked twice. This is handy if you need to have access to certain input parameters at different locations in the code.

The end user will get to see all the questions she needs to answer without having to worry about forgetting anything. The programmer can always run the program with tons of arguments and does not need any interactivity at all.

The concept can be adapted to various circumstances. Just as an idea, in the past I have implemented further helper functions like askYesNo() (which would expect either --argumentname or --no-argumentname) or askMultipleChoice().


Another thing which can be very handy is a --no-verify switch. Often the end user does not need to input anything but should acknowledge a certain fact (ex: “Bear in mind that X is only valid if Y.”). The program should pause and print some message but should obviously not halt during testing. A simple approach using the above setup in Perl could be:

...
GetOptions (
    \%opts,
    "no-verify",
...


approve(\%opts, "Read this please!");


sub approve {
    my ($opts, $msg) = @_;
    print "$msg\n";
    print "Press Enter to continue or CTRL-C to abort: ";
    if ($opt->{'no-verify'} == 0) {
        <STDIN>; # read cmd line only to force <Enter> to be pressed
    } else {
        # --no-verify was provided
    }
}

The developer would add --no-verify; the end user has to acknowledge every message by pressing Enter.


I love to have automated tests for my software so in production, I further expanded the presented framework to allow unit testing as follows. The ask() function should do one of the following (in the order of priority):

  • Return a non-empty argument specified by the caller (unit testing scenario).
  • Return the value specified on the command line (development scenario).
  • Ask & return the value specified interactivity (end user scenario).

Another advantage of the program’s possible non-interactivity is that it can be run in a cron job or via a remote shell. More often than not, a tool will not be run by a real person even if it was originally intended only as an interactive end user program. You can just tell your boss you’ve already implemented this feature!


As a side note, in 10 Usability Heuristics for User Interface Design by Jakob Nielsen, the Flexibility and efficiency of use pattern declares: “Accelerators – unseen by the novice user – may often speed up the interaction for the expert user such that the system can cater to both inexperienced and experienced users.” Well, guess what we just did?