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:
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!
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:
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:
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!