Skip to content

Promise-based portable interface for running shell commands

Notifications You must be signed in to change notification settings

asyncmax/pshell

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

24 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

pshell

Provides a simple Promise-based interface for running shell commands.

Basic usage

var shell = require("pshell");

shell("node --version").then(res => {
  console.log("exit code:", res.code);
});

/****** console output *******
 /bin/sh -c node --version
 v4.5.0
 exit code: 0
******************************/

Why?

Writing a shell script in JavaScript is:

  • Easier than bash script to most developers.
  • More portable (don't leave Windows users behind).
  • More powerful in managing child processes in asynchronous way.

More details

Don't echo the commands.

var shell = require("pshell");

shell("node --version", {echoCommand: false}).then(res => {
  console.log("exit code:", res.code);
});

/****** console output *******
 v4.5.0
 exit code: 0
******************************/

Capture the output as a string.

var shell = require("pshell");

shell("node --version", {echoCommand: false, captureOutput: true}).then(res => {
  console.log("stdout:", JSON.stringify(res.stdout));
  console.log("exit code:", res.code);
});

/****** console output *******
 stdout: "v4.5.0\n"
 exit code: 0
******************************/

Configure the global options so you don't need to specify the same options every time.

var shell = require("pshell");

shell.options.echoCommand = false;
shell.options.captureOutput = true;

Promise.all([
  shell("node --version"),
  shell("npm --version")
]).then(res => {
  process.stdout.write("Node version: " + res[0].stdout);
  process.stdout.write("NPM version: " + res[1].stdout);
});

/****** console output *******
 Node version: v4.5.0
 NPM version: 3.10.6
******************************/

You can get a pre-configured version of shell function by calling the context API. This is a good way to avoid modifying the global options. Other pshell users in the same process won't be affected.

var shell = require("pshell").context({echoCommand: false, captureOutput: true});

Promise.all([
  shell("node --version"),
  shell("npm --version")
]).then(res => {
  process.stdout.write("Node version: " + res[0].stdout);
  process.stdout.write("NPM version: " + res[1].stdout);
});

/****** console output *******
 Node version: v4.5.0
 NPM version: 3.10.6
******************************/

A non-zero exit code rejects the promise by default.

var shell = require("pshell").context({echoCommand: false});

// Using double quotes here is intentional. Windows shell supports double quotes only.
shell('node -e "process.exit(1)"').then(res => {
  console.log("exit code:", res.code);
}).catch(err => {
  console.error("error occurred:", err.message);
});

/****** console output *******
 error occurred: [Error: Process 26546 exited with code 1]
******************************/

Set ignoreError to true if you want to get the promise resolved instead.

var shell = require("pshell").context({echoCommand: false, ignoreError: true});

// Using double quotes here is intentional. Windows shell supports double quotes only.
shell('node -e "process.exit(1)"').then(res => {
  console.log("exit code:", res.code);
}).catch(err => {
  console.error("error occurred:", err.message);
});

/****** console output *******
 exit code: 1
******************************/

API

shell(command[, options])

Executes the command string using the system's default shell ("/bin/sh" on Unix, "cmd.exe" on Windows). An optional options object can be given to override the base options for this session.

Returns a promise that will be resolved with the result object when the command execution completes.

shell.options

An object represents the base options to be applied to every session executed from this context. You can modify fields of this object to change the default behavior.

shell.context([options])

Creates a new shell() function that is pre-configured with the specified options (combined with the current base options).

shell.spawn(exec[, args[, options]])

Executes a child process specified in exec directly without the system shell layer. args is an array of string arguments to be given to the process. Returns an object {childProcess, promise}. See ChildProcess for more details about Node's underlying child process object.

This is the base method for implementing shell() and shell.exec().

var shell = require("pshell").context({echoCommand: false});

var ret = shell.spawn("node", ["--version"], {captureOutput: true});

console.log("Node process ID:", ret.childProcess.pid);

ret.promise.then(function(res) {
  console.log("stdout:", JSON.stringify(res.stdout));
});

/****** console output *******
 Node process ID: 16372
 stdout: "v4.5.0\n"
******************************/

shell.exec(command[, options])

Same as shell() but returns an object {childProcess, promise} instead of a promise. Primary reason that you might want to use this function instead of shell() is probably to access the child process object for advanced use cases. However, keep in mind that the child process object returned by this method represents the system shell process, not the command process.

Calling shell(cmd, opts) is same as calling shell.exec(cmd, opts).promise.

shell.env(def)

Gets an object containing key-value pairs of environment variables combined with process.env. This is a low level function that can be used to construct an object for options.rawEnv.

In most cases, you will not need to use this function directly. Using options.env would be sufficient. See options.env and options.rawEnv for more details.

Options

options.Promise (default: null)

A Promise constructor to use instead of the system default one.

options.echoCommand (default: true)

If truthy, prints the command string to the console. If you specify a function, it gets called like func(exec, args) with this set to the options object. If you return false from the function, the command is not executed. childProcess and the result object of the promise are null in this case.

options.ignoreError (default: false)

If truthy, a non-zero exit code doesn't reject the promise so you can continue to the next steps.

options.captureOutput (default: false)

If truthy, stdout of the child process is captured as a string in res.stdout. If falsy, it is printed to the parent's stdout. If you specify a function, it gets called like func(buf) with this set to the options object. buf is an instance of Buffer containing the captured content. The return value from this function is set to res.stdout. You can use this feature for advanced use cases such as handling custom character encoding or parsing JSON.

var shell = require("pshell");

shell('node -e "console.log(JSON.stringify({a:1,b:2}))"', {
  echoCommand: false,
  captureOutput: function(buf) {
    return JSON.parse(buf.toString());
  }
}).then(function(res) {
  console.log("type:", typeof res.stdout);
  console.log("data:", res.stdout);
});

/****** console output *******
 type: object
 data: { a: 1, b: 2 }
******************************/

options.captureError (default: false)

If truthy, 'stderr' of the child process captured as a string in res.stderr. You can specify a function as in captureOutput options.

options.normalizeText (default: true)

If truthy, the end of line characters in a captured string are normalized to "\n". Only used when capturing to a string is enabled (by options.captureOutput and/or options.captureError).

options.env

An object containing key-value pairs of environment variables to set, in addition to the default process.env. Each env object in context chain is merged together to form a combined object and that object is given to shell.env() function to construct a final raw object, combined with process.env for Node's underlying API.

var shell = require("pshell");

shell("tape **/*.js", {
  env: {
    PATH: ["node_modules/.bin", process.env.PATH]
  }
});

This example effectively inserts "node_modules/.bin" at the beginning of PATH while passing down other values of process.env intact to the child process. If you specify an array as a value of an environment variable, all elements are joined into a string, separated with a path delimiter (':' on Unix, ';' on Windows).

Also, options.env does one more important job automatically for you. Because environment variable names are case insensitive on Windows, having multiple variables that are only different in case causes a problem. For example, simply cloning process.env and setting PATH will create a new key PATH in additon to an existing key Path on Windows because Path is the default key name. options.env prevents this from happening by removing matching keys regardless of case before setting a new value.

options.rawEnv

If you don't want the automatic features of env, you can specify a raw object in options.rawEnv. If options.rawEnv is specified, options.env is ignored and options.rawEnv is given to the child process as an entire set of environment variables.

options.inputContent (default: null)

You can specify a string or an instance of Buffer to supply the input data to stdin of the child process.

Node's child_process.spawn() options

In addition to the proprietary options above, the following options of child_process.spawn() also work.

  • cwd
  • arg0
  • stdio (if specified, inputContent, captureOutput and captureError options are ignored)
  • detached
  • uid
  • gid

Result object

This objects is given as a value when the promise returned by one of execution functions is resolved.

res.code (number)

The exit code of child process.

res.stdout (string or any)

The captured stdout of child process. This field exists only when options.captureOutput is truthy. This is a string value by default but you can override its behavior with your own custom handler.

res.stderr (string or any)

The captured stderr of child process. This field exists only when options.captureError is truthy. This is a string value by default but you can override its behavior with your own custom handler.

Develop & contribute

Setup

git clone https://github.com/asyncmax/pshell
cd pshell
npm install
npm run lint  # run lint
npm test      # run test

Coding style

  • Use two spaces for indentation.
  • Use double quotes for strings.
  • Use ES5 syntax for lib & test.

License

MIT

About

Promise-based portable interface for running shell commands

Resources

Stars

Watchers

Forks

Packages

No packages published