Command Line Node

Command-line Node.js... bye bye bash?

If I had to guess if node.js would every replace bash as a scripting language, I'd have to guess no. Needless to say, it's a very powerful scripting language used and loved by many. I certainly understand that.

That said, bash is a bit arcane in its syntax, it's not very easy to learn, and unless you're a die-hard Unix guru or devops type, then bash may not be the tool you'd naturally turn to in a time of need.

So what do you do should you find yourself in a situation where you need to write a quick Unix shell script? The obvious, and perhaps preferred, tool on the table is bash. But we just admitted that we stink at writing bash scripts right?

Fear not, for it's actually quite easy to write your shell script using node.js. You can even install your node script so that it behaves just like a normal unix binary command-line utility such as ls, grep, and curl. Let's see how to do this.

First, here are the high level steps:

  1. Create a project npm init
  2. Write the node script
  3. Pre-pend the shebang to "teach" the script to run like a command-line utility
  4. Install the script
  5. Test the script
  6. Improve

A Simple BASH Script

Suppose the task we want to solve is to rename files in a directory. Our target command takes three parameters:


rename   


  • criteria: the pattern to identify the target file(s) to rename
  • targetSubtring: the string within the target file(s) to rename
  • replaceString: the replacement string

The bash script looks something like this:



#!/bin/bash
 # rename.sh
 # basic file renamer

 criteria=$1
 targetSubstring=$2
 replaceString=$3

 for i in $( ls *$criteria* );
 do
     echo $i
     src=$i
     tgt=$(echo $i | sed -e "s/$targetSubstring/$replaceString/")
     mv $src $tgt
 done

To see this in action, assume we have some files in a directory that we are going to archive but before we do we want to tweak the names. The directory currently looks like this:

Assume also that we want to rename the word "finance" to "money". To do this the command will be as follows (assuming rename.sh is in the $PATH):

rename.sh finance finance money

Once the command runs, the files have been renamed and the directory listing should look like this:

Great, that's what we wanted. The problem now is the fact that the script is written in bash. Let's fix that by rewriting the script in node.js.

A Simple Node.js Script

Start by creating a node project (npm init), create an index.js file, and code up the logic. When done the first draft of the script looks something like this.



#!/usr/bin/env node
'use strict';

const fs = require('fs'),
      _ = require('lodash');

const input = process.argv.slice(2),
      criteria = input[0],
      targetSubstring = input[1],
      replaceString = input[2],
      cwd = process.cwd();

fs.readdir(cwd, function(err, files) {
  if (err) {
    return console.log('   +++ error reading files');
  }
  const targetFiles = _.filter(files, (f) => f.includes(criteria));
  _.each(targetFiles, function(file) {
    let newFilename = file.replace(targetSubstring, replaceString);
    fs.rename(file, newFilename);
  });
});

A couple things to note in this scripts:

  • lodash and fs are used for heavy-lifting
  • params.argv is used directly for parsing command-line arguments
  • #!/usr/bin/env node is used to "teach" the script to act similar to a regular command-line binary (more on this in a moment)

Now run and test the command:

node index.js finance finance money

And once again, the files are renamed and the directory should look like this:

So far so good, the script successfully renamed the files from finance to money and the command works. But, before we go ahead and publish this for all to use, let's now look at a couple finer points.

Shebang node/env

Line #1 of the new node.js script #!/usr/bin/env node is important. Normally when developing and debugging a node script, we'd run and test the script by executing node directly:

node