Reason #48 • February 17th, 2026

Shelling out with #system

Ruby offers various ways of shelling out to the command line. In the next few days we'll cover some of them, starting today with system.

The system method is a simple way to execute a command in the shell. It takes a command as a string and returns true if the command was executed successfully or a falsy value if it failed. STDOUT and STDERR from the command are printed directly to the console, so you can see the output of the command:

Ruby
system "echo Hello, world!"
# Hello, world!
# => true

system "ls non_existent_file"
# ls: non_existent_file: No such file or directory
# => false

system "no-such-command"
# => nil
      
JavaScript (node)
const { spawnSync } = require("child_process");

function system(command) {
  const result = spawnSync(command, { shell: true, stdio: "inherit" });
  return result.status === 0;
}

system("echo Hello, world!");
// Hello, world!
// => true

system("ls non_existent_file");
// ls: non_existent_file: No such file or directory
// => false

system("no-such-command");
// /bin/sh: no-such-command: command not found
// => false
      

We can also have system raise an exception if the command fails by passing exception: true:

Ruby
system "echo Hello, world!", exception: true
# Hello, world!
# => true

system "ls non_existent_file", exception: true
# ls: non_existent_file: No such file or directory
# => RuntimeError: Command failed with exit 1: ls
      
JavaScript (node)
const { spawnSync } = require("child_process");

function systemWithException(command) {
  const result = spawnSync(command, { shell: true, stdio: "inherit" });
  if (result.status !== 0) {
    throw new Error(`Command failed: ${command}`);
  }
  return true
}

systemWithException("echo Hello, world!");
// Hello, world!
// => true

systemWithException("ls non_existent_file");
// ls: non_existent_file: No such file or directory
// => Error: Command failed: ls non_existent_file
      

There are a whole bunch of other options available that I won't cover here, but feel free to check out the Execution Options documentation for more details.

You may have noticed that in these simple examples I provided the entire command as a single string, but system also supports passing the command and its arguments as separate strings, which can help avoid issues with shell escaping and protect against command injection:

Ruby
system "echo", "Hello, 'world'!"
# Hello, 'world'!
# => true

unsafe_input = 'hehe; rm -rf /'
system "echo", unsafe_input
# hehe; rm -rf /
# => true
      
JavaScript (node)
const { spawnSync } = require("child_process");

// NOTE: shell: false is the key to safely pass arguments
function systemWithSafeArguments(command, ...args) {
  return spawnSync(
    command, args, { shell: false, stdio: "inherit" }
  ).status === 0;
}

systemWithSafeArguments("echo", "Hello, 'world'!");
// Hello, 'world'!
// => true

const unsafeInput = 'hehe; rm -rf /';
systemWithSafeArguments("echo", unsafeInput);
// hehe; rm -rf /
// => true
      

You can also pass a leading env hash to set environment variables for the command:

Ruby
system({ "GREETING" => "Hello" }, "echo $GREETING, world!")
# Hello, world!
# => true
      
JavaScript (node)
const { spawnSync } = require("child_process");

function systemWithEnv(env, command) {
  return spawnSync(
    command,
    { shell: true, stdio: "inherit", env: { ...process.env, ...env } }
  ).status === 0;
}

systemWithEnv({ GREETING: "Hello" }, "echo $GREETING, world!");
// Hello, world!
// => true
      

History

Kernel#system has been around since Ruby's inception. The original inspiration comes from the POSIX system() function, which is a standard way to execute a command in the shell. Ruby may also have been influenced by Perl's system function.

Support for a leading ENV argument was added in Ruby 1.9. Support for the exception: true option was added in Ruby 2.6.

Reason #49 ?