Question

How to Capture Command Output in Ruby: system vs Backticks and Open3

rubysystemcall

Question

In Ruby, if I run a shell command using Kernel#system, how can I get the command's output?

For example:

system("ls")

I want to understand how to access what the command prints, rather than only running it.

Short Answer

By the end of this page, you will understand why system("ls") prints output to the terminal but does not return that output as a string. You will learn the difference between system, backticks, %x[], and Open3, and you will know which one to use when you need command output, exit status, or error output in Ruby.

Concept

Kernel#system is used to run an external command. Its main job is to execute the command and return whether it succeeded.

That means:

  • system(...) returns true if the command exits successfully
  • system(...) returns false if the command exits with a failure status
  • system(...) returns nil if the command could not be executed at all

A very important detail is that system does not return the command output. Instead, the command's standard output usually goes directly to the terminal.

If you need the output as a Ruby string, use one of these:

  • backticks: `ls`
  • %x[ls]
  • Open3.capture2, Open3.capture3, or related methods

Why this matters in real programming:

  • Scripts often need to read the output of another program
  • Build tools may need to parse command results
  • Deployment scripts may need both output and exit status
  • Error handling often requires access to stderr as well as stdout

So the key concept is this:

  • Use system when you want to run a command and check success/failure
  • Use backticks or Open3 when you want to capture output

Mental Model

Think of system like pressing a button on a machine and watching it work in front of you.

  • You press the button
  • The machine prints messages to the screen
  • At the end, it tells you only whether it succeeded

It does not hand you a copy of everything it printed.

Backticks and Open3 are more like giving the machine a recorder:

  • The command still runs
  • But now the output is captured for your Ruby program to inspect

So:

  • system = run it and observe
  • backticks = run it and capture the printed result
  • Open3 = run it and capture output with more control
Take Quiz

Syntax and Examples

The most common options are:

1. system

success = system("ls")
p success

This prints the ls output to the terminal, and then success will usually be true if the command worked.

2. Backticks

output = `ls`
puts output

This captures the command's standard output and stores it in output.

3. %x[]

output = %x[ls]
puts output

This is another syntax for backticks. It behaves the same way.

4. Open3.capture2

require "open3"

stdout, status = Open3.capture2("ls")
puts stdout
puts status.success?

This captures standard output and also gives you the exit status.

5. Open3.capture3

require "open3"

stdout, stderr, status = Open3.capture3("ls /missing_folder")
puts "STDOUT: #{stdout}"
puts "STDERR: #{stderr}"
puts "SUCCESS: #{status.success?}"

This is useful when you need both output streams.

Beginner-friendly example

files = `ls`

if $?.success?
  puts "Command worked"
  puts files
else
  puts "Command failed"
end

Explanation:

  • `ls` runs the command and captures its output
  • $? contains information about the last child process
  • $?.success? tells you whether the command succeeded

Step by Step Execution

Consider this example:

require "open3"

stdout, stderr, status = Open3.capture3("ruby -v")

puts "Output: #{stdout}"
puts "Errors: #{stderr}"
puts "Success?: #{status.success?}"

Step by step:

  1. require "open3"

    • Loads Ruby's standard library for running external commands with more control.
  2. Open3.capture3("ruby -v")

    • Runs the command ruby -v
    • Captures anything written to standard output
    • Captures anything written to standard error
    • Returns the process exit status
  3. stdout, stderr, status = ...

    • stdout becomes the normal command output
    • stderr becomes the error output
    • status becomes a Process::Status object
  4. puts "Output: #{stdout}"

    • Prints the captured output string
  5. puts "Errors: #{stderr}"

    • Prints the captured error string, which may be empty
  6. puts "Success?: #{status.success?}"

    • Prints true if the command exited successfully

Example result might look like:

Output: ruby 3.3.0
Errors: 
Success?: true

This shows the main idea clearly: the command output is now available inside Ruby variables, not just printed directly to the terminal.

Real World Use Cases

Capturing command output is common in many Ruby programs.

1. Checking Git information

branch = `git branch --show-current`.strip
puts "Current branch: #{branch}"

Useful in deployment scripts or developer tools.

2. Reading system information

hostname = `hostname`.strip
puts hostname

Useful in server automation.

3. Running build or test commands

output = `rspec`
puts output

Useful when a script needs to log or analyze test results.

4. Validating external tools

require "open3"

stdout, stderr, status = Open3.capture3("node -v")
if status.success?
  puts "Node is installed: #{stdout.strip}"
else
  puts "Node is not available: #{stderr}"
end

Useful in setup scripts.

5. Parsing command results

count = `ls | wc -l`.strip
puts "File count: #{count}"

Useful when integrating shell tools into a Ruby workflow.

Take Quiz

Real Codebase Usage

In real projects, developers usually choose the command API based on what they need.

Common patterns

Use system for simple success/failure

abort("Deploy failed") unless system("cap production deploy")

This is common in scripts where the command should stream directly to the terminal.

Use backticks for quick stdout capture

version = `git rev-parse --short HEAD`.strip

This is convenient for short scripts, but less flexible for error handling.

Use Open3 when output and errors both matter

require "open3"

stdout, stderr, status = Open3.capture3("convert input.png output.jpg")
raise stderr unless status.success?

This is common in production code because it gives better control.

Guard clause for command failure

require "open3"

stdout, stderr, status = Open3.capture3("my_command")
raise "Command failed: #{stderr}" unless status.success?

puts stdout

Validation before using output

branch = `git branch --show-current`.strip
raise "No branch name found" if branch.empty?

Logging and debugging

stdout, stderr, status = Open3.capture3("some_command")
warn stderr unless stderr.empty?
puts stdout if status.success?

In larger codebases, Open3 is often preferred because it supports:

  • separate stdout and stderr
  • exit-status checks
  • safer command argument handling
  • more predictable error handling

Common Mistakes

1. Expecting system to return output

Broken example:

output = system("ls")
puts output

Why this is wrong:

  • output will be true, false, or nil
  • it will not contain the file list

Use this instead:

output = `ls`
puts output

2. Forgetting to remove trailing newlines

branch = `git branch --show-current`
puts "Branch: #{branch}!"

This often produces awkward formatting because command output usually ends with a newline.

Better:

branch = `git branch --show-current`.strip
puts "Branch: #{branch}!"

3. Ignoring command failure

output = `some_missing_command`
puts output

This may give empty output or unexpected results. Always check status when failure matters.

output = `some_command`
if $?.success?
  puts output
else
  warn "Command failed"
end

4. Not capturing stderr when needed

Some commands write errors to stderr, not stdout.

require "open3"
stdout, stderr, status = Open3.capture3("ls /missing")

If you only read stdout, you may miss the real error.

5. Using shell interpolation unsafely

Broken example:

filename = gets.chomp
output = `cat #{filename}`

This can be dangerous if user input contains shell characters.

Safer approach:

require "open3"
stdout, stderr, status = Open3.capture3("cat", filename)

Passing arguments separately avoids many shell-related problems.

Comparisons

ApproachReturnsCaptures stdout?Captures stderr?Best for
system("cmd")true, false, or nilNoNoRunning a command and checking success
`cmd`StringYesNoQuick output capture
%x[cmd]StringYesNoSame as backticks
Open3.capture2("cmd")stdout, statusYesNoOutput plus exit status
Open3.capture3("cmd")stdout, stderr, statusYesYesFull control

system vs backticks

  • system is for execution with direct terminal output
  • backticks are for capturing output into a variable

Backticks vs Open3

  • backticks are shorter and simpler
  • Open3 is better when you need error output and robust status handling

%x[] vs backticks

  • They are effectively the same feature
  • %x[] can be easier to read when the command contains quotes
Take Quiz

Cheat Sheet

# Run command, print directly to terminal, return success/failure
ok = system("ls")

# Capture stdout as a string
output = `ls`
output = %x[ls]

# Check exit status after backticks
output = `ls`
$?.success?

# Capture stdout and exit status
require "open3"
stdout, status = Open3.capture2("ls")

# Capture stdout, stderr, and exit status
require "open3"
stdout, stderr, status = Open3.capture3("ls /missing")

# Remove trailing newline from command output
text = `hostname`.strip

Rules to remember

  • system does not return command output
  • backticks return stdout as a string
  • use .strip when you want clean output
  • use Open3.capture3 when stderr matters
  • check status.success? or $?.success? when failure matters

Edge cases

  • A command may print nothing and still succeed
  • A command may fail and write the useful message to stderr only
  • system may return nil if the command cannot be started
  • shell interpolation can be unsafe with user input

FAQ

How do I get the output of a shell command in Ruby?

Use backticks like output = `ls` or use Open3.capture2 / Open3.capture3 for more control.

Why does system("ls") not return the file list?

Because system is designed to run the command and return success or failure, not the printed output.

What does system return in Ruby?

It returns true for success, false for command failure, and nil if the command could not be executed.

Should I use backticks or Open3 in Ruby?

Use backticks for simple scripts when you only need stdout. Use Open3 when you also need stderr or explicit exit-status handling.

How do I capture stderr in Ruby?

Use Open3.capture3, which returns stdout, stderr, and status separately.

How do I know if a backtick command succeeded?

Check $?.success? right after running the command.

Is %x[] the same as backticks in Ruby?

Yes. %x[] is an alternative syntax for executing a command and capturing stdout.

What is the safest way to run external commands in Ruby?

For robust code, prefer Open3 with separate command arguments instead of building shell strings from user input.

Take Quiz

Mini Project

Description

Build a small Ruby command runner that asks for a command, executes it, and shows the captured output, captured errors, and whether the command succeeded. This demonstrates the practical difference between simply running a command and actually reading its result inside Ruby.

Goal

Create a Ruby script that runs an external command and prints stdout, stderr, and exit status clearly.

Requirements

  • Use Ruby's Open3 library
  • Run at least one external command and capture its output
  • Print standard output and standard error separately
  • Show whether the command succeeded
  • Clean up output with .strip where appropriate
Take Quiz

Keep learning

Related questions

View more