Skip to main content

How to Parse Command Line Arguments (CLI, Node.JS, Commander.js)

In this article, I am going to show you how to parse command line arguments using NodeJS.

This article builds on my previous article: How to Create a Command Line Interface with Node.JS (CLI, Zero Setup)

In that article, I showed you how to set up and distribute a basic cli (command line interface) app.  Here I will show you how to create a version that can parse command-line arguments using a package called Commander.js (link in references at the end of this article).

These steps were written for and tested on a Mac.

info

Some of the steps below are condensed and adapted from my previous article. Refer to that for more details.

Step 1. Create a project folder

To get started, create and set up a project folder:

  • Open a terminal window
  • Create and change to a folder for a new project:
mkdir -p ~/projects/javascript/my-tools-cli
cd ~/projects/javascript/my-tools-cli

Step 2. Initialize the project

To initialize the project, run this command:

npm init -y

Step 3. Edit package.json

  • Open package.json in your favorite code editor
  • Replace the line for the "main" property with this line:
"bin": "./index.js",

Add this line above that and save the file:

"type": "module",

Step 4. Add some packages

To add some packages to the project, run this command:

npm install --save commander cowsay figlet

That adds three packages:

  • commander (Commander.js) - for parsing the command line
  • cowsay - a package we'll use to demo how to hide a command (in this case an "easter egg")
  • figlet - a package for turning letters into ASCII banners (useful for printing a version header, etc).

Step 5. Create index.js

From the command line run the following to create index.js (or you can just use your code editor):

touch index.js

To make it runnable from the command line, open index.js in a code editor, put this line at the top (it must be the first line) and save the file:

#!/usr/bin/env node

Step 6. Import the packages

To import the installed packages into index.js, add this code and save the file:

import { Command, Option } from "commander";
import { default as figlet } from "figlet";

Step 7. Setup the command parser

To set up the command parser, start by updating index.js with the code below and saving it:

#!/usr/bin/env node

import { Command, Option } from "commander";
import { default as figlet } from "figlet";

function main() {

const program = new Command();

program
.version("0.0.1", "-v, --version", "output the current version")
.addHelpText("before", figlet.textSync("MY TOOLS"))
.helpOption("-h, --help", "display help")
.description("My Tools")

program.parse(process.argv).opts();
}

main();

To test the code, run the following at the command line:

node index.js

Note that nothing is output.  I will show you how to change that later. But hopefully, it compiled and ran.

Now try again by passing in arguments:

node index.js --version

node index.js --help

node index.js --bogus

The commands should do the following for each flag:

  • version - prints the version info
  • help - prints the help info
  • bogus - prints an error because that flag is not an option

I've also added a call using the commander addHelpText method with the "before" argument.  It uses the figlet package to print out an ASCII banner of the tool name before printing the help text.

Step 8. Add arguments and flags

Add this code above the line that parses the arguments in the main function, and save the file:

.argument("<message>", "message to parse")
.option(" --banner", "create a banner out of the message")
.action((message, options) => {
console.log(`DEBUG: ${message},\n${JSON.stringify(options,null,2)}`)
});

Run this command from the command line:

node index.js "hello world" --banner

You should see output like this:

DEBUG: hello world,
{
"banner": true
}

If you look at the code, it is telling you that the message parameter was passed to the function defined in .action as the first argument, and an options object was passed as the second argument.

The options argument has a property (.banner) that matches the name of the flag and is set to true.  You can use that to figure out if a flag has been passed. Then you can apply it to the first parameter (message).

  • To do that, create a new file:
touch option-banner.js
  • Add this code to that file and save it:
import { default as figlet } from "figlet";

export function optionBanner(message) {
let output = figlet.textSync(message);
console.log(output);
}

The code exports a function called optionBanner. It takes the message passed to it as an argument and echos it as an ASCII banner using the figlet package. This is the same package used to echo the title of the package to the console when help is called.

  • Open index.js and add this import line under the other import lines:
import { optionBanner } from "./option-banner.js"
  • Update the .action method to now call the optionBanner function if the flag is passed in and save the file (note that I commented out the debug line):
.action((message, options) => {
// console.log(`DEBUG: ${message},\n${JSON.stringify(options,null,2)}`)
if (options.banner) optionBanner(message);
});

Now run it again and you should see the message output as an ASCII banner:

node index.js "hello world" --banner

Finally, verify that the --banner option has been added to the help:

node index.js --help

You can add flags that don't display in the help.  

For this example, create a new file:

touch option-moo.js

Open the file in a code editor, add this code, and save it:

import * as cowsay from "cowsay";

export function optionMoo(message) {
let output = cowsay.say({ text: message });
console.log(output);
}

The code uses the cowsay package to write a cow with a message bubble to the console.

To use the new function, add this line to the bottom of the import section in index.js and save it:

import { optionMoo } from "./option-moo.js";

Update the .action method to setup the argument and call the function, then save the file:

.argument("<message>", "message to parse")
.option(" --banner", "create a banner out of the message")
.addOption(new Option(" --moo").hideHelp())
.action((message, options) => {
// console.log(`DEBUG: ${message},\n${JSON.stringify(options,null,2)}`)
if (options.banner) optionBanner(message);
if (options.moo) optionMoo(message);
});
  • The addOption call uses .hideHelp to hide the --moo flag from the help screen
  • the if( options.moo ) test calls the optionMoo function, passing in the message if the flag was used

To test it, run this command:

node index.js "hello world" --moo
  • Verify that the new option does NOT appear in the help:
node index.js --help

Step 10. Add a command

As your tool adds features, you may need to start organizing flags by commands.

In this step, I will show you how to add a command for file operations.

  • In index.js, add this code above the .parse call and save the file:
program
.command("file")
.description("process a file")
.argument("<filename>", "target file")
// node index.js file temp.txt --count
.option(" --count", "count words in a file")
.action((filename, options) => {
console.log(`DEBUG: ${filename},\n${JSON.stringify(options,null,2)}`)
});

The code does the following:

  • adds a command called "file"
  • adds a help description for the command
  • adds an argument (filename)
  • adds a file command flag called count
  • defines another .action handler for handling file commands

To see how the file command is added to the help screen, run this command:

node index.js --help

You should see this at the bottom of the help:

Commands:
file [options] <filename> process a file

Right now the command will just print debug info to the console.  To test that, run this command:

node index.js file temp.txt --count

That will print the filename argument (temp.txt) and it will also pass an object with a count property set true.  This is similar to how parameters were passed in the first .action call.

To test the new command, create a new file called option-file-count.js:

touch option-file-count.js
  • Paste this code into option-file-count.js and save the file:
import * as fs from 'fs';

export async function optionFileCount(filename) {

fs.readFile(filename, 'utf8', (err, data) => {
if (err) {
console.error(err);
return;
}
let count = data.trim().split(/\s+/).length;
console.log(count)
});
}

The code does the following:

  • Reads the file
  • Parses it into tokens
  • Logs a count value to the console
tip

This example will only work for text files that are small enough to be loaded in one call.

Update index.js

  • Open index.js and add this line to the bottom of the import section:
import { optionFileCount } from "./option-file-count.js"
  • Update the code for the command .action handler to process the count call and save it (I've commented out the debug line):
program
.command("file")
.description("process a file")
.argument("<filename>", "target file")
// node index.js file temp.txt --count
.option(" --count", "count words in a file")
.action((filename, options) => {
// console.log(`DEBUG: ${filename},\n${JSON.stringify(options, null, 2)}`)
if (options.count) optionFileCount(filename);
});

To test it do the following:

  • Create a temp file:
echo "This is a test." > ~/temp.txt
  • Run this command to test using that file:
node index.js file ~/temp.txt --count

Step 11. Add a .gitignore file

In the next step, I'm going to suggest that you check the code into GitHub or your favorite git projects host.  Before doing that, add a .gitgnore file to avoid checking in extra code:

  • Create .gitignore:
touch .gitignore
  • Open it in an editor, add these lines, and save the file:
node_modules/
npm-debug.log
.DS_Store

Step 12. Add more helper text

In my previous article, I showed how a user can install the latest code automatically and run it.  Here is an example of adding help instructions for the user:

  • Check your code into a new repo on GitHub, and note the path
  • Add this .addHelperText call below the .description call, and save the file:
  • Be sure to change YOUR_GHUSER to your GitHub username:
.addHelpText(
"after",
"\nTo setup an alias:\n" +
"\n 1. Add this to ~/.zshrc" +
'\n\n alias mytools="npm exec --yes -- https://github.com/YOUR_GHUSER/my-tools-cli";\n' +
"\n 2. save it" +
"\n 3. $ source ~/.zshrc" +
"\n 4. $ mytools --help" +
`\n`
)
  • The call sets the first argument to "after"
  • Verify that the text appears after the rest of the help text:
node index --help

Example

The example that was created for this article can be found here:

Conclusion

In this article, you learned how to:

  • Parse the arguments sent to a command-line interface (cli) utility
  • Define a help screen that the user can call with a flag
  • Define handlers for additional flags
  • Group commands to make your tools more efficient

References

  • npmjs.com/package/commander [1]