Skip to content

Terminate parsing with a double hypen

Lloyd Brookes edited this page Jan 16, 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 you want to collect two things - an application name 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.

Method 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 probably 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' ] }

Method 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 but ambiguity problems will arise if we later add an -m option to our list of option definitions.

Method 3: 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 have an -m option defined potentially causing ambiguity issues as -m might also be passed as a value to --args. This issue is cancelled out by stopAtFirstUnknown which causes any arg including and beyond the first unknown to be added to _unknown. So, in this case our first -m correct sets the more option and our second -m is collected in the _unknown property.

$ node tmp/--.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 --args passed we 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 -- as the opening arg in the list of argument 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 }