Executing Shell Commands in Node.js and Capturing Output

Nov 29, 2025 · Programming · 11 views · 7.8

Keywords: Node.js | Shell | Child Process | Exec | Spawn | Asynchronous Programming

Abstract: This article provides a comprehensive overview of executing shell commands in Node.js using the child_process module. It covers the exec and spawn methods, asynchronous handling with callbacks and async/await, error management, input/output streaming, and killing processes, with practical code examples.

Introduction

Node.js, as a server-side JavaScript runtime, often requires interaction with the underlying operating system, including executing shell commands. This article delves into methods for running Unix terminal commands in Node.js and capturing their output, utilizing the built-in child_process module.

Using child_process.exec for Simple Commands

The exec function is suitable for commands that produce output immediately and have limited size. It spawns a shell to process the command.

const { exec } = require('child_process');

function executeCommand(command, callback) {
  exec(command, (error, stdout, stderr) => {
    if (error) {
      console.error(`Execution error: ${error}`);
      return;
    }
    callback(stdout);
  });
}

// Example usage
executeCommand('ls', (output) => {
  console.log('Command output:', output);
});

In this example, the executeCommand function runs a shell command and passes the standard output to the callback. Error handling is included to manage command failures.

Asynchronous Handling with Promisify and Async/Await

For modern Node.js versions, using util.promisify with async/await provides a cleaner way to handle asynchronous operations.

const { promisify } = require('util');
const exec = promisify(require('child_process').exec);

async function getGitUser() {
  try {
    const nameOutput = await exec('git config --global user.name');
    const emailOutput = await exec('git config --global user.email');
    return {
      name: nameOutput.stdout.trim(),
      email: emailOutput.stdout.trim()
    };
  } catch (error) {
    console.error('Error retrieving git user:', error);
    throw error;
  }
}

// Usage
getGitUser().then(user => {
  console.log('Git user:', user);
}).catch(error => {
  console.error('Failed:', error);
});

This approach uses promises, making the code more readable and allowing error handling with try/catch.

Using child_process.spawn for Continuous Output

For commands with continuous or long-running output, such as ping or npm install, the spawn function is more appropriate as it does not buffer the output and allows real-time processing.

const { spawn } = require('child_process');

const command = spawn('ping', ['google.com']);

command.stdout.on('data', (data) => {
  console.log('Output:', data.toString());
});

command.stderr.on('data', (data) => {
  console.error('Error:', data.toString());
});

command.on('close', (code) => {
  console.log(`Command exited with code ${code}`);
});

Here, the output is captured as it is produced, enabling immediate feedback.

Passing Input to Commands

Some commands require input through STDIN. The spawn method allows writing to the stdin stream.

const { spawn } = require('child_process');

const command = spawn('grep', ['apple']);

command.stdin.write('1. pear\n');
command.stdin.write('2. grapes\n');
command.stdin.write('3. apple\n');
command.stdin.write('4. banana\n');
command.stdin.end();

command.stdout.on('data', (data) => {
  console.log('Filtered output:', data.toString());
});

This example sends input to the grep command and captures the filtered output.

Killing Child Processes

To terminate a long-running command, use the kill method.

const { exec } = require('child_process');

const command = exec('sleep 100');

setTimeout(() => {
  command.kill();
  console.log('Command killed');
}, 1000);

This ensures that processes do not run indefinitely, useful for timeouts.

Conclusion

In summary, Node.js provides robust tools via the child_process module for executing shell commands. Use exec for simple, immediate commands, and spawn for continuous output. Leverage modern JavaScript features like async/await for better asynchronous handling, and always include error management and process control for production applications.

Copyright Notice: All rights in this article are reserved by the operators of DevGex. Reasonable sharing and citation are welcome; any reproduction, excerpting, or re-publication without prior permission is prohibited.