How to run shell commands in Node.js

Nov 30, 2023#node#scripts

Bash is great, but when it comes to writing more complex scripts, many people prefer a more convenient programming language. JavaScript is a perfect choice, but the Node.js standard library requires additional hassle before using.

Node.js allows for more flexibility in how commands are executed and how their outputs are processed. You can easily stream data, handle events, and manipulate results, providing a higher level of control over the execution process compared to traditional shell scripts.

This capability opens up a range of possibilities, including system administration tasks, deployment automation, interaction with system tools, Git operations, and package management.

There are several ways to do that, depending on your needs and preference.

  1. Using the exec() function: This function creates a new shell and executes a given command. The output from the execution is buffered, which means kept in memory, and is available for use in a callback.
const { exec } = require("child_process");

exec("ls -la", (error, stdout, stderr) => {
  if (error) {
    console.log(`error: ${error.message}`);
  if (stderr) {
    console.log(`stderr: ${stderr}`);
  console.log(`stdout: ${stdout}`);
  1. Using the spawn() function: This function launches a new process with a given command and arguments, and returns a stream object that you can use to listen for data events.
const { spawn } = require("child_process");
const ping = spawn("ping", [""]);

ping.stdout.on("data", (data) => {
  console.log(`stdout: ${data}`);

ping.stderr.on("data", (data) => {
  console.log(`stderr: ${data}`);

ping.on("close", (code) => {
  console.log(`child process exited with code ${code}`);
  1. Using the ShellJS package, which is a portable (Windows/Linux/macOS) implementation of Unix shell commands on top of the Node.js API. You can use it to eliminate your shell script’s dependency on Unix while still keeping its familiar and powerful commands.
var shell = require('shelljs');

if (!shell.which('git')) {
  shell.echo('Sorry, this script requires git');

// Copy files to release dir
shell.rm('-rf', 'out/Release');
shell.cp('-R', 'stuff/', 'out/Release');
  1. Using zx package which provides useful wrappers around child_process, escapes arguments and gives sensible defaults.
#!/usr/bin/env zx

await $`cat package.json | grep name`

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

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

let name = 'foo bar'
await $`mkdir /tmp/${name}`