Read more

Running external commands with Open3

Tobias Kraze
March 09, 2017Software engineer at makandra GmbH

There are various ways to run external commands from within Ruby, but the most powerful ones are Open3.capture3 and Open3.popen3. Since those can do almost everything you would possibly need in a clean way, I prefer to simply always use them.

Illustration web development

Do you need DevOps-experts?

Your development team has a full backlog? No time for infrastructure architecture? Our DevOps team is ready to support you!

  • We build reliable cloud solutions with Infrastructure as code
  • We are experts in security, Linux and databases
  • We support your dev team to perform
Read more Show snapshot

Behind the scenes, Open3 actually just uses Ruby's spawn command, but gives you a much better API.


Basic usage is

require 'open3'

stdout_str, error_str, status = Open3.capture3('/some/binary', 'with', 'some', 'args')
if status.success?
  # okay
  raise "did not work"

Open3 will raise an error if the binary cannot run at all, but won't raise an error if the binary returns an exit code > 0. This will instead be reflected in status.

stdout_str, error_str are strings containing the command's output and error output.

Shell expansion

If you have to run the command through the user's shell, pass the command as a string instead of an array:

Open3.capture3('/some/binary with some args')

This is not recommended though, due to additional overhead and the potential for shell injections.


If the command reads from stdin and you want to feed it some data, you can use

Open3.capture3('/some/binary', stdin_data: 'this is read from stdin')

ENV variables

To pass additional ENV variables to the command, pass them as a hash in the first argument:

Open3.capture3({'VAR' => 'value'}, '/some/binary')

You can use unsetenv_others: true to clear all other ENV variables.

Working directory

Run the command with a different working directory, by using chdir:

Open3.capture3('/some/binary', chdir: '/some/directory')

File descriptors

Ruby's spawn command gives you full control over file descriptors (and lets you point stdin to a file, or merge stdout and stderr) etc. Open3 however does not allow you to do this. If you need it, you have to use spawn directly.


If you are not interested in the error input you can use the capture2 method instead. The parameters are the same as for capture3.

stdout_str, status = Open3.capture2('/some/binary', 'with', 'some', 'args')


Another alternative would be capture2e. This will combine the outputs from stdout and stderr into one.

stdout_and_stderr_str, status = Open3.capture2e('/some/binary', 'with', 'some', 'args')


This version allows your code to interact with the external command while it is running. Always use it in its block form:

Open3.popen3('/some/command') do |stdin, stdout, stderr, wait_thr|
  stdin.puts "This is sent to the command"
  stdin.close                # we're done
  stdout_str =   # read stdout to string. note that this will block until the command is done!
  stderr_str =   # read stderr to string
  status = wait_thr.value    # will block until the command finishes; returns status that responds to .success? etc

This form is useful for long running commands you have to interact with, or if you want to pipe large amounts of data without keeping everything in memory.

However, it is tricky to use this correctly in all circumstances. If the command produces lots of output, for example, you need to make sure the stdout and stderr streams are continuously read, otherwise the command will block. You would need to use threads (or to achieve this.

Tobias Kraze
March 09, 2017Software engineer at makandra GmbH
Posted by Tobias Kraze to makandra dev (2017-03-09 11:08)