This is a card in Dave's Virtual Box of Cards.

Running system commands from Ruby

Created: 2024-04-27

Back to ruby

So, like a classic Unix citizen, you can simply enclose a command in backticks to run it and capture its output:

$ irb
irb(main):001:0> puts `ls -l`
total 48
-rw-r--r-- 1 dave users  1996 Apr 27 09:52 README.md
-rw-r--r-- 1 dave users  2783 Apr 27 13:03 log.txt
-rwxr-xr-x 1 dave users 12835 Apr 27 19:35 ratsync
-rw-r--r-- 1 dave users   254 Apr 14 09:40 settings_test
-rw-r--r-- 1 dave users   317 Apr 15 14:18 source_repo.rb
-rw-r--r-- 1 dave users   583 Apr 15 14:18 sourcetest.rb
-rwxr-xr-x 1 dave users  8512 Apr 27 19:46 test.rb
=> nil

Awesome.

But sometimes you need to get the command’s exit status, etc.

I prefer the %x method of quoting a system command and executing it:

cmd = "ls -al"
output = %x(#{cmd})

After this line, output contains whatever the command wrote to standard out (STDOUT).

You can check the exit status with:

if Process.last_status.success?
    puts "It succeeded!"
else
    puts "It failed!"
end

The exit status number can be had with Process.last_status.exitstatus.

If you want to also capture output written to standard error (STDERR), this should work on all POSIX systems:

output = %x(#{cmd} 2>&1)

As part of a test script

I wrote a little function to do all the bells and whistles for a little test script and I’m about to not need that anymore, so I wanted to capture it for possible future reference.

Note that this function is (obviously?) tailored for testing purposes. If you just want it to return the captured output and status in a struct or something, it could be much more compact than this.

def run_cmd(cmd, should_pass=true)
  begin
    output = %x(#{cmd} 2>&1)

    death_info = <<~INFO
      Command: #{cmd}
      Exit status: #{Process.last_status.exitstatus}
      Output: #{output}
    INFO

    if Process.last_status.success?
      if !should_pass
        puts "Command SHOULD have exited with error (but didn't).#{death_info}"
        #exit 1
      end
    else
      if should_pass
        puts "Command exited with error.#{death_info}"
        #exit 1
      end
    end

    if block_given?
      # Run the block. It is responsible for exiting as needed.
      yield output
    end

  rescue => exception
    if should_pass
      puts "Command failed completely! #{death_info}"
      puts exception.to_s
      #exit 1
    end
  end
end

(The full script had other test-friendly features like printing success/failures with ANSI colors, etc. But I’ve stripped those from this function to keep this example from getting unweildy.)

Here’s some examples and their output to get an idea of how this test method worked:

run_cmd("echo foo")

    (silently succeeds)

run_cmd("echo foo") do |out|
  puts "Just showing that we got the output: '#{out.strip}'"
end

    Just showing that we got the output: 'foo'

run_cmd("echo baz") do |out|
  out.strip!
  if out != "bar"
    puts "Should have returned 'bar' but got '#{out}'"
    #exit 1
  end
end

    Should have returned 'bar' but got 'baz'

# Note: the Unix command 'false' always exits with an error status.
run_cmd("false")

    Command exited with error.
    Command: false
    Exit status: 1
    Output:

run_cmd("false", false)

    (silently succeeds)

run_cmd("echo foo", false)

    Command SHOULD have exited with error (but didn't).
    Command: echo foo
    Exit status: 0
    Output:
    foo

run_cmd("barf")

    Command exited with error.
    Command: barf
    Exit status: 127
    Output:
    sh: line 1: barf: command not found