execa vs shell-quote vs shelljs vs cross-env vs child_process vs node-cmd
Node.js Command Execution Libraries Comparison
1 Year
execashell-quoteshelljscross-envchild_processnode-cmdSimilar Packages:
What's Node.js Command Execution Libraries?

These libraries provide various functionalities for executing shell commands and managing child processes in Node.js applications. They facilitate the interaction with the operating system's command line, enabling developers to run scripts, manage environment variables, and handle command output in a more efficient and user-friendly manner. Each package has its unique features and use cases, catering to different needs in command execution and process management.

Package Weekly Downloads Trend
Github Stars Ranking
Stat Detail
Package
Downloads
Stars
Size
Issues
Publish
License
execa87,358,3267,036324 kB123 months agoMIT
shell-quote25,587,3582923.5 kB83 months agoMIT
shelljs7,927,38414,316212 kB101-BSD-3-Clause
cross-env7,914,9066,364-14 years agoMIT
child_process855,908159-49 years agoISC
node-cmd33,086285-74 years agoMIT
Feature Comparison: execa vs shell-quote vs shelljs vs cross-env vs child_process vs node-cmd

Ease of Use

  • execa:

    execa provides a clean and modern API that is easy to understand and use. It simplifies the process of executing commands and handling their output, making it a favorite among developers.

  • shell-quote:

    shell-quote is straightforward for parsing and quoting shell commands, making it easy to construct commands safely without dealing with the intricacies of shell syntax.

  • shelljs:

    shelljs mimics Unix shell commands in Node.js, making it intuitive for developers familiar with shell scripting. It provides a familiar syntax for executing shell commands.

  • cross-env:

    cross-env is extremely easy to use, allowing you to set environment variables in a single command without worrying about platform differences. It simplifies cross-platform compatibility in scripts.

  • child_process:

    The child_process module offers a low-level API that provides flexibility but can be cumbersome for simple tasks. It requires more boilerplate code to handle streams and errors effectively.

  • node-cmd:

    node-cmd is designed for simplicity, allowing you to execute commands with minimal setup. It abstracts away the complexities of child processes, making it user-friendly for quick tasks.

Error Handling

  • execa:

    execa provides built-in promise rejection for command failures, making error handling straightforward. It allows developers to easily catch errors using async/await or .catch().

  • shell-quote:

    shell-quote focuses on quoting and parsing commands, so it does not handle errors from command execution. Error management must be handled separately.

  • shelljs:

    shelljs provides error handling similar to Unix shell, where commands throw errors if they fail. This makes it easier to manage errors in scripts.

  • cross-env:

    cross-env does not directly handle errors from commands; it simply sets environment variables. Error handling must be managed in the commands executed after setting the variables.

  • child_process:

    Error handling in child_process requires manual management of streams and exit codes, which can be complex for beginners. Developers must implement their own error handling logic.

  • node-cmd:

    node-cmd returns the command output and error messages, but does not provide advanced error handling features. Developers need to check the output manually for errors.

Platform Compatibility

  • execa:

    execa is platform-agnostic and works seamlessly across different operating systems, making it a reliable choice for cross-platform command execution.

  • shell-quote:

    shell-quote is platform-independent as it focuses on quoting and parsing commands rather than executing them, making it universally applicable.

  • shelljs:

    shelljs provides a Unix-like shell experience in Node.js, but its commands may not work on Windows without a compatible shell environment.

  • cross-env:

    cross-env is specifically designed for cross-platform compatibility, allowing developers to set environment variables in a way that works on both Windows and Unix-like systems.

  • child_process:

    child_process is built into Node.js and works consistently across platforms, but the commands executed may vary depending on the operating system.

  • node-cmd:

    node-cmd is also platform-independent, but the commands executed must be valid on the target platform, which may lead to compatibility issues if not handled properly.

Output Management

  • execa:

    execa simplifies output management by returning a promise that resolves with the output of the command, making it easy to access stdout and stderr without additional setup.

  • shell-quote:

    shell-quote does not manage output as it is only responsible for parsing and quoting commands. Output handling must be done separately in the execution context.

  • shelljs:

    shelljs provides a rich set of commands that return output similar to Unix, allowing for straightforward output management in scripts.

  • cross-env:

    cross-env does not manage output as it is solely focused on setting environment variables. Output management must be handled in the commands executed afterward.

  • child_process:

    child_process allows for detailed control over output streams, enabling developers to handle stdout and stderr separately, but requires more code to manage effectively.

  • node-cmd:

    node-cmd returns the output of the command as a string, making it easy to capture and use, but lacks advanced output management features.

Scripting Capabilities

  • execa:

    execa can be used in scripts to execute commands easily and handle their output, making it suitable for modern JavaScript scripting needs.

  • shell-quote:

    shell-quote is not a scripting library; it is a utility for safely constructing command strings, which can be used in scripts but does not provide execution capabilities.

  • shelljs:

    shelljs is a powerful tool for writing shell scripts in Node.js, providing a comprehensive set of commands that mimic Unix shell, making it ideal for complex scripting tasks.

  • cross-env:

    cross-env is not a scripting tool but rather a utility for setting environment variables, making it less relevant for scripting capabilities.

  • child_process:

    child_process provides the foundational capabilities for scripting by allowing the execution of any command, but requires more effort to build complex scripts.

  • node-cmd:

    node-cmd is useful for quick command execution in scripts but lacks advanced scripting features found in more comprehensive libraries.

How to Choose: execa vs shell-quote vs shelljs vs cross-env vs child_process vs node-cmd
  • execa:

    Opt for execa if you want a promise-based wrapper around child_process that simplifies the API and provides better handling of child process output and errors. It is ideal for modern JavaScript applications that utilize async/await.

  • shell-quote:

    Choose shell-quote when you need to safely parse and quote shell command strings. It is particularly useful for constructing commands dynamically and avoiding shell injection vulnerabilities.

  • shelljs:

    Select shelljs if you want a complete shell scripting solution for Node.js, providing a rich set of shell commands that mimic Unix shell commands. It is great for writing scripts that require a variety of shell operations.

  • cross-env:

    Select cross-env when you need to set environment variables across different operating systems in a consistent manner. It is particularly useful for scripts that need to run in both Unix and Windows environments.

  • child_process:

    Choose child_process if you need a built-in, low-level API for spawning child processes and handling streams directly. It provides maximum flexibility but requires more boilerplate code.

  • node-cmd:

    Use node-cmd for a straightforward way to execute shell commands without dealing with streams. It is simple and easy to use for quick command execution tasks.

README for execa
execa logo

Coverage Status

Process execution for humans





Execa runs commands in your script, application or library. Unlike shells, it is optimized for programmatic usage. Built on top of the child_process core module.


One of the maintainers @ehmicky is looking for a remote full-time position. Specialized in Node.js back-ends and CLIs, he led Netlify Build, Plugins and Configuration for 2.5 years. Feel free to contact him on his website or on LinkedIn!


Features

Install

npm install execa

Documentation

Execution:

Input/output:

Advanced usage:

Examples

Execution

Simple syntax

import {execa} from 'execa';

const {stdout} = await execa`npm run build`;
// Print command's output
console.log(stdout);

Script

import {$} from 'execa';

const {stdout: name} = await $`cat package.json`.pipe`grep name`;
console.log(name);

const branch = await $`git branch --show-current`;
await $`dep deploy --branch=${branch}`;

await Promise.all([
	$`sleep 1`,
	$`sleep 2`,
	$`sleep 3`,
]);

const directoryName = 'foo bar';
await $`mkdir /tmp/${directoryName}`;

Local binaries

$ npm install -D eslint
await execa({preferLocal: true})`eslint`;

Pipe multiple subprocesses

const {stdout, pipedFrom} = await execa`npm run build`
	.pipe`sort`
	.pipe`head -n 2`;

// Output of `npm run build | sort | head -n 2`
console.log(stdout);
// Output of `npm run build | sort`
console.log(pipedFrom[0].stdout);
// Output of `npm run build`
console.log(pipedFrom[0].pipedFrom[0].stdout);

Input/output

Interleaved output

const {all} = await execa({all: true})`npm run build`;
// stdout + stderr, interleaved
console.log(all);

Programmatic + terminal output

const {stdout} = await execa({stdout: ['pipe', 'inherit']})`npm run build`;
// stdout is also printed to the terminal
console.log(stdout);

Simple input

const getInputString = () => { /* ... */ };
const {stdout} = await execa({input: getInputString()})`sort`;
console.log(stdout);

File input

// Similar to: npm run build < input.txt
await execa({stdin: {file: 'input.txt'}})`npm run build`;

File output

// Similar to: npm run build > output.txt
await execa({stdout: {file: 'output.txt'}})`npm run build`;

Split into text lines

const {stdout} = await execa({lines: true})`npm run build`;
// Print first 10 lines
console.log(stdout.slice(0, 10).join('\n'));

Streaming

Iterate over text lines

for await (const line of execa`npm run build`) {
	if (line.includes('WARN')) {
		console.warn(line);
	}
}

Transform/filter output

let count = 0;

// Filter out secret lines, then prepend the line number
const transform = function * (line) {
	if (!line.includes('secret')) {
		yield `[${count++}] ${line}`;
	}
};

await execa({stdout: transform})`npm run build`;

Web streams

const response = await fetch('https://example.com');
await execa({stdin: response.body})`sort`;

Convert to Duplex stream

import {execa} from 'execa';
import {pipeline} from 'node:stream/promises';
import {createReadStream, createWriteStream} from 'node:fs';

await pipeline(
	createReadStream('./input.txt'),
	execa`node ./transform.js`.duplex(),
	createWriteStream('./output.txt'),
);

IPC

Exchange messages

// parent.js
import {execaNode} from 'execa';

const subprocess = execaNode`child.js`;
await subprocess.sendMessage('Hello from parent');
const message = await subprocess.getOneMessage();
console.log(message); // 'Hello from child'
// child.js
import {getOneMessage, sendMessage} from 'execa';

const message = await getOneMessage(); // 'Hello from parent'
const newMessage = message.replace('parent', 'child'); // 'Hello from child'
await sendMessage(newMessage);

Any input type

// main.js
import {execaNode} from 'execa';

const ipcInput = [
	{task: 'lint', ignore: /test\.js/},
	{task: 'copy', files: new Set(['main.js', 'index.js']),
}];
await execaNode({ipcInput})`build.js`;
// build.js
import {getOneMessage} from 'execa';

const ipcInput = await getOneMessage();

Any output type

// main.js
import {execaNode} from 'execa';

const {ipcOutput} = await execaNode`build.js`;
console.log(ipcOutput[0]); // {kind: 'start', timestamp: date}
console.log(ipcOutput[1]); // {kind: 'stop', timestamp: date}
// build.js
import {sendMessage} from 'execa';

const runBuild = () => { /* ... */ };

await sendMessage({kind: 'start', timestamp: new Date()});
await runBuild();
await sendMessage({kind: 'stop', timestamp: new Date()});

Graceful termination

// main.js
import {execaNode} from 'execa';

const controller = new AbortController();
setTimeout(() => {
	controller.abort();
}, 5000);

await execaNode({
	cancelSignal: controller.signal,
	gracefulCancel: true,
})`build.js`;
// build.js
import {getCancelSignal} from 'execa';

const cancelSignal = await getCancelSignal();
const url = 'https://example.com/build/info';
const response = await fetch(url, {signal: cancelSignal});

Debugging

Detailed error

import {execa, ExecaError} from 'execa';

try {
	await execa`unknown command`;
} catch (error) {
	if (error instanceof ExecaError) {
		console.log(error);
	}
	/*
	ExecaError: Command failed with ENOENT: unknown command
	spawn unknown ENOENT
			at ...
			at ... {
		shortMessage: 'Command failed with ENOENT: unknown command\nspawn unknown ENOENT',
		originalMessage: 'spawn unknown ENOENT',
		command: 'unknown command',
		escapedCommand: 'unknown command',
		cwd: '/path/to/cwd',
		durationMs: 28.217566,
		failed: true,
		timedOut: false,
		isCanceled: false,
		isTerminated: false,
		isMaxBuffer: false,
		code: 'ENOENT',
		stdout: '',
		stderr: '',
		stdio: [undefined, '', ''],
		pipedFrom: []
		[cause]: Error: spawn unknown ENOENT
				at ...
				at ... {
			errno: -2,
			code: 'ENOENT',
			syscall: 'spawn unknown',
			path: 'unknown',
			spawnargs: [ 'command' ]
		}
	}
	*/
}

Verbose mode

await execa`npm run build`;
await execa`npm run test`;
execa verbose output

Custom logging

import {execa as execa_} from 'execa';
import {createLogger, transports} from 'winston';

// Log to a file using Winston
const transport = new transports.File({filename: 'logs.txt'});
const logger = createLogger({transports: [transport]});
const LOG_LEVELS = {
	command: 'info',
	output: 'verbose',
	ipc: 'verbose',
	error: 'error',
	duration: 'info',
};

const execa = execa_({
	verbose(verboseLine, {message, ...verboseObject}) {
		const level = LOG_LEVELS[verboseObject.type];
		logger[level](message, verboseObject);
	},
});

await execa`npm run build`;
await execa`npm run test`;

Related

Maintainers