Running system commands from Ruby
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