Reason #51 • February 20th, 2026

Shelling out with spawn

Today we'll keep it short and sweet by covering yet another way to shell out in Ruby: spawn.

You can think of spawn as system but non-blocking. It starts the command in a separate child process and immediately returns the PID of the spawned process, allowing your Ruby code to continue executing while the command runs in the background:

Ruby
pid = spawn <<~SCRIPT
  echo "Hello, world!"
  sleep 5
  echo "Goodbye, world!"
SCRIPT

puts "Waiting for the command to finish..."

Process.wait(pid)

puts "Command finished!"

# Output:
# Waiting for the command to finish...
# Hello, world!
# Goodbye, world!
# Command finished!
      
JavaScript (node)
const { spawn } = require("child_process");

const child = spawn("sh", ["-c", `
  echo "Hello, world!"
  sleep 5
  echo "Goodbye, world!"
`], { stdio: "inherit" });

console.log("Waiting for the command to finish...");
child.on("exit", () => {
  console.log("Command finished!");
});

// Output:
// Waiting for the command to finish...
// Hello, world!
// Goodbye, world!
// Command finished!
      

If you want to capture the status of the command, you can use Process.wait2:

Ruby
pid = spawn "echo Hello, world!"
pid, status = Process.wait2(pid)
puts "Exit status: #{status.exitstatus}"

# Output:
# Hello, world!
# Exit status: 0

pid = spawn "ls non_existent_file"
pid, status = Process.wait2(pid)
puts "Exit status: #{status.exitstatus}"

# ls: non_existent_file: No such file or directory
# Exit status: 2
    

If you want to capture the output of the command, you can redirect it to a file and read the file after the command finishes:

Ruby
pid = spawn "echo Hello, world!", out: "stdout.log"
Process.wait(pid)
output = File.read("stdout.log")
# => "Hello, world!\n"

pid = spawn "ls non_existent_file", out: "stdout.log", err: "stderr.log"
Process.wait(pid)
output = File.read("stderr.log")
# => "ls: non_existent_file: No such file or directory\n"
    

In general spawn has the same argument structure as system, so you can also pass multiple arguments to it to avoid the shell, pass an initial ENV hash, etc.

History

The spawn method has been part of Ruby since its inception and has evolved in the same way as system over the years. It was inspired by the POSIX spawn function.

For once, this function is NOT available in Perl, so we can more clearly attribute this inclusion to one of the original design goals of Ruby: to be an object-oriented UNIX.

Enjoy your weekend!