Editor's note: If you like CLIs, you should check out oclifconf taking place on Friday, May 31st in San Francisco. It’s the first community get-together for oclif! Space is limited so let us know soon if you are interested in joining.
What is it that makes working from the command line so empowering? It can feel archaic at times, sure, but when you remember the right sequence of words, characters, and symbols for what you’re trying to do, it hits you with a sense of accomplishment and mastery over your tools that no graphical interface can compete with.
So what better way to continue your adventures as a developer than by developing your own CLI tool?
In this post, we’ll go over what type of parameters CLI commands take—also known as "flags", "arguments", and sometimes "options.” Then, we’ll start you off with oclif, the CLI framework that makes it easy to create new CLI commands!
The Syntax of a CLI Command
Any command line interface command has a few standard "parts of speech.” As a user of CLI tools, knowing these parts of speech can help you make fewer typos. It can also help you understand complex commands other people share with you more quickly (like these). If you are designing a CLI tool it is even more important to understand these parts of speech, so you can come up with the most ergonomic interface for your users. Yes, a CLI is a user interface!
Some of you may recognize diagrams like the one below from elementary or primary school. Fortunately, understanding how CLI commands are structured isn’t going to feel like this.
CLI commands are pretty straightforward compared to the typical English sentence.
For starters, let’s look at the parameters that appear to the right of CLI commands. Sure, there are many ways you can pass data to a CLI command, but these three types of parameters to the "right" of the command might be the most common: argument, long flag, and short flag. These two formats for flags are the standard for GNU-style flags. Not all CLIs follow this convention, but it has become the most popular style on Unix-like and POSIX compliant operating systems.
What better way for us to start than with the ls
command? It’s one of the most common and simplest commands on Unix-like operating systems. It simply lists the contents of a directory.
Command
$ ls
This command, ls
, works on its own, as a standalone command. Without any parameters, this command will list the contents of the current directory.
Argument
$ ls .
But you can do the same thing with an argument! Turns out that ls .
and ls
are the same thing, with ls
simply using an implied .
directory. For those that don’t remember or don’t know, .
always refers to the current directory.
But now, the argument syntax makes it possible for you to pass any directory path to ls
, and to get a look at what’s in there.
$ ls /home/casey/code/some-repo-name
An argument is anything to the right of a command that is not a flag (we’ll get to flags next). And fortunately, an argument can come before or after flags–it can coexist with them happily.
Long Flag
To list files that are normally hidden (like ~/.bashrc
), you can use a flag on the ls
command. ls --all
is the long flag form. A long flag always uses a double dash, and it is always represented by multiple characters.
$ ls --all
$ ls . --all
Short Flag
There is also a short flag form of this flag: ls -a
. The a
is short for all
in this case. A short flag always uses a single dash, and it is always represented by a single letter.
$ ls -a
$ ls . -a
Short flags can stack too, so you don't need a separate dash for each one. Order does not matter for these, unless passing a flag argument.
$ ls -la
Flag Argument
Many flags accept an option called a "flag argument" (not to be confused with a "command argument"). In general a command's parameters can be in any order, but flags that accept options must have the option directly after the flag. That way, the command doesn’t get confused by non-flag arguments.
For an example, here the -x
flag does not accept an option but the -f
flag does. archive.tar
is the option being passed to -f
. Both of these are valid.
$ tar -x -f archive.tar
$ tar -xf archive.tar
A flag and its option can be separated by a space or an equals sign =
. Interestingly, short flags (but not long flags) can even skip the space, although many people find it much easier to read with the space or equals sign.
These three are all valid and equivalent.
$ tar -f archive.tar
$ tar -f=archive.tar
$ tar -farchive.tar
Long flags must have a space or equals sign to separate the flag from its option.
$ git log --pretty=oneline
$ git log --pretty oneline
Other Ways of Passing Data
We've covered parameters, which are arguments, long flags, and short flags. There are two other ways to pass data to a command: environment variables ("env vars"), or standard input ("stdin"). These won't be covered in this blog post, but check out the links to learn more about them.
Building a New Command With oclif
Scenario: we want to design an oclif command that takes an input like "Casey", and returns "hi, Casey!". There are many ways the user could pass this in. Here we show an example of each type of input using an argument, a long flag, and a short flag.
First, let’s get started with oclif
. Getting a CLI app going is very, very straightforward with it. Open up your terminal and type the following, which will use npx
to run oclif
and then create a new CLI. npx
is a pretty useful command to simplify running CLI tools and other executables hosted on the npm registry.
$ npx oclif single greet-me
We won’t go into the details of the single
(vs multi
) argument above. Check out the oclif documentation for more about this.
Now, you’ll get the chance to specify some details of your new CLI, including the command name. When it asks you, just press enter, choosing the deafult. It’ll take the greet-me
argument you passed into the above command. You can choose the default for most of the questions it asks you. The answers won't make much of a difference for this simple tutorial. However, they are very important to answer accurately if you will be sharing your CLI command with others.
? npm package name: greet-me
? command bin name the CLI will export: greet-me
...
Created greet-me in /home/casey/code/greet-me
Now that we have things set up, let’s check out what’s happening in /greet-me/src/index.ts
, where all the important argument and flag-handling code for your CLI will live.
const {Command, flags} = require('@oclif/command')
class GreetMeCommand extends Command {
async run() {
const {flags} = this.parse(GreetMeCommand)
const name = flags.name || 'world'
this.log(`hello ${name} from ./src/index.js`)
}
}
GreetMeCommand.description = `Describe the command here
...
Extra documentation goes here
`
GreetMeCommand.flags = {
// add --version flag to show CLI version
version: flags.version({char: 'v'}),
// add --help flag to show CLI version
help: flags.help({char: 'h'}),
name: flags.string({char: 'n', description: 'name to print'}),
// flag with no value (-f, --force)
force: flags.boolean({char: 'f'}),
}
module.exports = GreetMeCommand
What we can see here is that it accepts a few different flag names out the gate (version
, name
, help
, and force
) by registering them in the flags
object.
…
version: flags.version({char: 'v'}),
…
Here, with the version
flag, the key acts as the ‘version’ long flag name, and on the right side of the expression, we use the method's in oclif
’s flags
module to register a flag, a type it’ll return, and the short flag name.
Now, we’re ready to rock: let’s see just how many things oclif
handles out of the box by running the CLI. Right now, it’s only available with a slightly awkward command.
$ ./bin/run
But npm allows us to symlink this to the name of our CLI.
$ npm link
...
$ greet-me
> hello world from ./src/index.ts
Excellent! Try passing your name in using -n
or --name
next–and see if there are any other ways oclif
will let you pass in arguments.
SIGTERM
While that’s all we’re going to cover in this blog post, oclif has a growing community and its code is open source so there are lots of other ways to learn more. Here are some links to continue exploring oclif.
- An episode of the Code[ish] podcast about oclif with Jeff Dickey, one of the creators of oclif, and Nahid Samsami, Heroku’s PM for oclif
- oclif’s GitHub repository