Executing commands in ruby

Neeraj Singh

By Neeraj Singh

on October 18, 2012

Ruby allows many different ways to execute a command or a sub-process. In this article we are going to see some of them.


1. Returns standard output

backtick returns the standard output of the operation.

1output = `ls`
2puts "output is #{output}"

Result of above code is

1$ ruby main.rb
2output is lab.rb

2. Exception is passed on to the master program

Backtick operation forks the master process and the operation is executed in a new process. If there is an exception in the sub-process then that exception is given to the main process and the main process might terminate if exception is not handled.

In the following case I am executing xxxxx which is not a valid executable name.

1output = `xxxxxxx`
2puts "output is #{output}"

Result of above code is given below. Notice that puts was never executed because the backtick operation raised exception.

1$ ruby main.rb
2main.rb:1:in ``': No such file or directory - xxxxxxx (Errno::ENOENT)
3	from main.rb:1:in `<main>'

3. Blocking operation

Backtick is a blocking operation. The main application waits until the result of backtick operation completes.

4. Checking the status of the operation

To check the status of the backtick operation you can execute $?.success?

1output = `ls`
2puts "output is #{output}"
3puts $?.success?

Notice that the last line of the result contains true because the backtick operation was a success.

1$ ruby main.rb
2output is lab.rb

backtick returns STDOUT. backtick does not capture STDERR . If you want to learn about STDERR then checkout this excellent article .

You can redirect STDERR to STDOUT if you want to capture STDERR using backtick.

1output = `grep hosts /private/etc/* 2>&1`

5. String interpolation is allowed within the ticks

1cmd = 'ls'

6. Different delimiter and string interpolation

%x does the same thing as backtick. It allows you to have different delimiter.

1output = %x[ ls ]
2output = %x{ ls }

backtick runs the command via shell. So shell features like string interpolation and wild card can be used. Here is an example.

1$ irb
2> dir = '/etc'
3> %x<ls -al #{dir}>
4=> "lrwxr-xr-x@ 1 root  wheel  11 Jan  5 21:10 /etc -> private/etc"


system behaves a bit like backtick operation. However there are some differences.

First let's look at similarities.

1. Blocking operation

Just like backtick, system is a blocking operation.

2. Eats up all exceptions

system eats up all the exceptions. So the main operation never needs to worry about capturing an exception raised from the child process.

1output = system('xxxxxxx')
2puts "output is #{output}"

Result of the above operation is given below. Notice that even when exception is raised the main program completes and the output is printed. The value of output is nil because the child process raised an exception.

1$ ruby main.rb
2output is

3. Checking the status of the operation

system returns true if the command was successfully performed ( exit status zero ) . It returns false for non zero exit status. It returns nil if command execution fails.

1system("command that does not exist")  #=> nil
2system("ls")                           #=> true
3system("ls | grep foo")                #=> false


exec replaces the current process by running the external command. Let's see an example.

Here I am in irb and I am going to execute exec('ls').

1$ irb
2e1.9.3-p194 :001 > exec('ls')
3lab.rb  main.rb
5nsingh ~/dev/lab 1.9.3

I see the result but since the irb process was replaced by the exec process I am no longer in irb .

Behind the scene both system and backtick operations use fork to fork the current process and then they execute the given operation using exec .

Since exec replaces the current process it does not return anything if the operation is a success. If the operation fails then `SystemCallError is raised.


sh actually calls system under the hood. However it is worth a mention here. This method is added by FileUtils in rake. It allows an easy way to check the exit status of the command.

1require 'rake'
2sh %w(xxxxx) do |ok, res|
3   if !ok
4     abort 'the operation failed'
5   end


If you are going to capture stdout and stderr then you should use popen3 since this method allows you to interact with stdin, stdout and stderr .

I want to execute git push heroku master programmatically and I want to capture the output. Here is my code.

1require 'open3'
2cmd = 'git push heroku master'
3Open3.popen3(cmd) do |stdin, stdout, stderr, wait_thr|
4  puts "stdout is:" + stdout.read
5  puts "stderr is:" + stderr.read

And here is the output. It has been truncated since rest of output is not relevant to this discussion.

1stdout is:
2stderr is:
3-----> Heroku receiving push
4-----> Ruby/Rails app detected
5-----> Installing dependencies using Bundler version 1.2.1

The important thing to note here is that when I execute the program ruby lab.rb I do not see any output on my terminal for first 10 seconds. Then I see the whole output as one single dump.

The other thing to note is that heroku is writing all this output to stderr and not to stdout .

Above solution works but it has one major drawback. The push to heroku might take 10 to 20 seconds and for this period we do not get any feedback on the terminal. In reality when we execute git push heroku master we start seeing result on our terminal one by one as heroku is processing things.

So we should capture the output from heroku as it is being streamed rather than dumping the whole output as one single chunk of string at the end of processing.

Here is the modified code.

1require 'open3'
2cmd = 'git push heroku master'
3Open3.popen3(cmd) do |stdin, stdout, stderr, wait_thr|
4  while line = stderr.gets
5    puts line
6  end

Now when I execute above command using ruby lab.rb I get the output on my terminal incrementally as if I had typed git push heroku master .

Here is another example of capturing streaming output.

1require 'open3'
2cmd = 'ping www.google.com'
3Open3.popen3(cmd) do |stdin, stdout, stderr, wait_thr|
4  while line = stdout.gets
5    puts line
6  end

In the above case you will get the output of ping on your terminal as if you had typed ping www.google.com on your terminal .

Now let's see how to check if command succeeded or not.

1require 'open3'
2cmd = 'ping www.google.com'
3Open3.popen3(cmd) do |stdin, stdout, stderr, wait_thr|
4  exit_status = wait_thr.value
5  unless exit_status.success?
6    abort "FAILED !!! #{cmd}"
7  end


popen2e is similar to popen3 but merges the standard output and standard error .

1require 'open3'
2cmd = 'ping www.google.com'
3Open3.popen2e(cmd) do |stdin, stdout_err, wait_thr|
4  while line = stdout_err.gets
5    puts line
6  end
8  exit_status = wait_thr.value
9  unless exit_status.success?
10    abort "FAILED !!! #{cmd}"
11  end

In all other areas this method works similar to popen3 .


Kernel.spawn executes the given command in a subshell. It returns immediately with the process id.

1irb(main)> pid = Process.spawn("ls -al")
2=> 81001