Skip to content

Terminate parsing with a double hypen

Lloyd Brookes edited this page Jun 24, 2018 · 4 revisions

A common pattern with command line applications is to use -- to terminate parsing, leaving all remaining arguments to be treated as unknown values even if they begin with a hyphen.

Example

Say your command line application needs to collect two pieces of information - the name of a separate application and a list of arguments to run it with. We expect passing a list of program arguments (e.g. -m My message etc) to be problematic as some of the values passed may resemble options themselves, causing ambiguity issues.

Potential solution 1: use --option=value notation

const commandLineArgs = require('command-line-args')
const optionDefinitions = [
  { name: 'app' },
  { name: 'args', multiple: true }
]

const options = commandLineArgs(optionDefinitions)
console.log(options)

Now, we would like to run this script passing in git to --app and merge --squash -m My commit message to --args...

$ node example.js --app git --args merge --squash -m My commit message

This script will crash, throwing an UNKNOWN_OPTION: Unknown option: --squash exception. This is due to ambiguity - some of the values (--squash and -m) passed to --args look like options themselves.

We can address the ambiguity by using --option=value notation but it's possibly too much to expect the user to append --args= to each value resembling an option.

$ node example.js --app git --args merge --args=--squash --args=-m My commit message
{ app: 'git',
  args: [ 'merge', '--squash', '-m', 'My', 'commit', 'message' ] }

Potential solution 2: use partial: true

Another method is to use partial parsing, defining only the --app option and leaving everything else to be returned in _unknown.

const commandLineArgs = require('command-line-args')
const optionDefinitions = [
  { name: 'app' }
]

const options = commandLineArgs(optionDefinitions, { partial: true })
console.log(options)

Here's a usage example.

$ node example.js --app git merge --squash -m My commit message
{ _unknown: [ 'merge', '--squash', '-m', 'My', 'commit', 'message' ],
  app: 'git' }

This gives us the output we need, with our app name and application arguments grouped together under _unknown but ambiguity problems will arise if we later add an -m option to our list of option definitions.

Solution: use stopAtFirstUnknown with --

Ambiguity issues can be avoided by using stopAtFirstUnknown.

const commandLineArgs = require('command-line-args')
const optionDefinitions = [
  { name: 'app' },
  { name: 'more', type: Boolean, alias: 'm' }
]

const options = commandLineArgs(optionDefinitions, { stopAtFirstUnknown: true })
console.log(options)

In this case, we now have an -m option defined potentially causing ambiguity issues as -m might also be passed in the arbitrary list of application args we wish to collect. This issue is cancelled out by stopAtFirstUnknown which causes any arg including and beyond the first unknown to be added to the _unknown list. So, in this case our first -m correctly sets the more flag and our second -m is collected within the _unknown list.

$ node example.js --app git -m merge --squash -m My commit message
{ _unknown: [ 'merge', '--squash', '-m', 'My', 'commit', 'message' ],
  app: 'git',
  more: true }

However, if the user changes the order of args passed making -m the first in the list of arbitrary application args passed we again have an ambiguity issue.

$ node example.js --app git -m -m My commit message
/Users/lloyd/Documents/75lb/command-line-args/lib/option.js:41
        throw err
        ^

ALREADY_SET: Singular option already set [more=true]

The way to avoid this (and all remaining issues) is to use the conventional -- string as the opening arg in the list of values you'd like the parser to ignore. The final command would look something like this.

$ node example.js --app git -m -- merge --squash -m My commit message
{ _unknown: [ '--', 'merge', '--squash', '-m', 'My', 'commit', 'message' ],
  app: 'git',
  more: true }