5 ways to run shell commands from Ruby

Every so often I have the need to execute a command line application from a Ruby script. And every single time I fail to remember what the different command-executing methods Ruby provides us with do.

This post is primarily a brain dump to aid my failing memory, and it was triggered by an issue with my Redmine Github Hook plugin where STDERR messages were not being logged.

The goal of this is to figure out how to run a shell command and capture all its output - both STDOUT and STDERR - so that the output can be used in the calling script.

err.rb

The test script I’ll be running basically outputs two lines, one on STDOUT, the other on STDERR:

#!/usr/bin/env ruby
puts "out"
STDERR.puts "error"

Kernel#` (backticks)

Returns the standard output of running cmd in a subshell. The built-in syntax %x{…} uses this method. Sets $? to the process status.

>> `./err.rb`
err
=> "out\n"

Kernel#exec

Replaces the current process by running the given external command.

>> exec('./err.rb')
out
err

Kernel#system

Executes cmd in a subshell, returning true if the command was found and ran successfully, false otherwise. An error status is available in $?. The arguments are processed in the same way as for Kernel::exec.

>> system('./err.rb')
out
err
=> true

IO#popen

Runs the specified command string as a subprocess; the subprocess’s standard input and output will be connected to the returned IO object.

>> output = IO.popen('./err.rb')
=> #<IO:0x1017511b8>
>> err
output.readlines
=> ["out\n"]

Open3#popen3

Open stdin, stdout, and stderr streams and start external executable.

>> require 'open3'
=> true
>> stdin, stdout, stderr = Open3.popen3('./err.rb')
=> [#<IO:0x101769da8>, #<IO:0x101769d30>, #<IO:0x101769c68>]
>> stdout.readlines
=> ["out\n"]
>> stderr.readlines
=> ["err\n"]

Open3#capture3

An alternative to Open#popen3, which is perhaps simpler, is using Open3#capture3. It basically wraps Open3#popen3 and returns STDERR and STDOUT as strings:

>> require 'open3'
=> true
>> stdout, stderr = Open3.capture3('./err.rb')
=> ["out\n", "error\n", #<Process::Status: pid 68154 exit 0>]
>> stdout
=> ["out\n"]
>> stderr
=> ["err\n"]

Alternative: Using ‘nix redirection

Another alternative to Open#popen3, that still gives us both STDOUT and STDERR, is using standard output redirection:

>> `./err.rb 2>&amp;1`
=> "err\nout\n"

This gives you both STDOUT and STDERR in one big string, which might be perfectly fine if you don’t require the granular control that popen3 brings to the table.

I am guessing this method would work for IO#popen as well as for the backticks.

What to choose?

There is a great discussion about the different ways to call shell commands over at Stack Overflow, and in particular an excellent flowchart that can help you decide what method to use when launching a subprocess.