DailyJS

Unix and Node: Command-line Arguments

Alex R. Young

Subscribe

@dailyjs

Facebook

Google+

tutorials node documentation unix essay

Unix and Node: Command-line Arguments

Posted by Alex R. Young on .
Featured

tutorials node documentation unix essay

Unix and Node: Command-line Arguments

Posted by Alex R. Young on .

Shebang Bootcamp

Writing an executable script with Node begins with the shebang: #!, followed by the path to a Node executable. Not all Unix systems use the same file system layout, and not all users want to install Node in the same place. Since the shebang must specify an absolute path, a common way around this is to introduce a level of indirection through env:

#!/usr/bin/env node

This can be seen in many popular Node modules, and in other scripting languages too. If I try and run a script like this in my shell, I'll get an error:

➜ ./echo.js
zsh: permission denied: ./echo.js  

I have read permission on the file, but it isn't executable:

➜ ls -l
-rw-r--r--  1 alex  wheel  21  1 Mar 17:24 echo.js
➜ chmod u+x ./echo.js 
➜ ls -l
total 8  
-rwxr--r--  1 alex  wheel  21  1 Mar 17:24 echo.js

Here I've used chmod to set the executable flag for the user that owns the file. Most people use the octal notation for setting permissions, which looks complicated but isn't as hard as it seems.

Argument Vector

Command-line arguments are available through the process global in an array called argv. This term comes from "argument vector", influenced by C where it's conventional to name the argument count and argument vector this way:

int main(int argc, char *argv[])  

Technically these parameters could be named anything, but this is a strongly followed convention.

Compared to C, JavaScript's array is a handy type for representing arguments:

#!/usr/bin/env node

console.log('Arguments:', process.argv.length);  
console.log(process.argv);  

Running this script with no arguments shows what the script was run with:

Arguments: 2  
[ 'node', '/private/tmp/unix-node/echo.js' ]

Conversely, passing in arguments looks like this:

➜ ./echo.js --text JavaScript in the shell
Arguments: 7  
[ 'node',
  '/private/tmp/unix-node/echo.js',
  '--text',
  'JavaScript',
  'in',
  'the',
  'shell' ]

Handling more complex arguments takes a little bit of work, so it's often preferable to offload the effort to a suitable module. However, some scripts have simple requirements, so array manipulation of process.argv may suffice. This example is from Node's cluster.js file:

exports.start = function() {  
  amMaster = true;

  if (process.argv.length < 1) {
    console.error('Usage: node cluster script.js');
    process.exit(1);
  }

  var args = process.argv.slice(2);
  var scriptFilename = args.shift();

The length of process.argv is validated to ensure the expected option has been supplied, and process.exit(1) is called if validation fails. This is interesting because an argument has been supplied to process.exit. Why 1?

Exit Status

Unix and Unix-like systems use a convention that a non-zero exit status from a program is an error. In C, EXIT_FAILURE is a macro that can be used to denote this, and on POSIX systems this value is 1. Changing the previous example to return a non-zero exit code looks like this:

#!/usr/bin/env node

console.log('Arguments:', process.argv.length);  
console.log(process.argv);

if (process.argv.length < 3) {  
  process.exit(1);
}

Running this script with no arguments should return 1. However, look what happens:

➜ ./echo.js 
Arguments: 2  
[ 'node', '/private/tmp/unix-node/echo.js' ]

... nothing? There's no error message or anything! This is actually a good thing -- think back to the philosophies behind Unix, in particular Doug McIlroy's quote:

Write programs to handle text streams, because that is a universal interface.

In a world based around text streams, we don't want default behaviour that's too noisy. If someone built upon this script, they'd rather deal with a concrete error status. This value can be obtained in most shells by reading $?:

➜ echo $?
1  

Option Parsing Libraries

The two most popular option parsing libraries for Node are Optimist (License: MIT/X11, npm: optimist) and Commander.js (GitHub: visionmedia / commander.js, License: MIT, npm: commander) by TJ Holowaychuk. These are both well-maintained libraries by active members of the Node community.

Optimist has its own argv object that can deal with grouped, long, and short options, and can even display usage:

#!/usr/bin/env node
var argv = require('optimist')  
    .usage('Usage: $0 -x [num] -y [num]')
    .demand(['x','y'])
    .argv;

console.log(argv.x / argv.y);  

Commander.js has a chainable API that's like a DSL for option processing:

#!/usr/bin/env node

var program = require('commander');

program  
  .version('0.0.1')
  .option('-p, --peppers', 'Add peppers')
  .option('-P, --pineapple', 'Add pineapple')
  .option('-b, --bbq', 'Add bbq sauce')
  .option('-c, --cheese [type]', 'Add the specified type of cheese [marble]', 'marble')
  .parse(process.argv);

console.log('you ordered a pizza with:');  
if (program.peppers) console.log('  - peppers');  
if (program.pineapple) console.log('  - pineappe');  
if (program.bbq) console.log('  - bbq');  
console.log('  - %s cheese', program.cheese);  

The program's usage is automatically generated.

Conclusion

In Node, powerful option parsing is only an npm install away. The built-in process.argv array is also convenient and easy to manage for simple situations. Just remember to exit programs with conventional status codes, or angry shell scripters may fill up your GitHub issues before you know it!