2

I know how to run an external process as there are many ways to do so. But how do I capture its output and exit code to the variables? These, the most popular ways of running a process, don't work as I want:

a = `ls -l` # "a" captures only output
b = system "ls -l " # "b" captures only exit code
Incerteza
  • 32,326
  • 47
  • 154
  • 261

5 Answers5

5

Most appropriate is to read $?.exitstatus:

a = `ls -l`        # gets output
b = $?.exitstatus  # gets exit code

Test:

`true`
# => ""
$?.exitstatus
# => 0
`false`
# => ""
$?.exitstatus
# => 1
$?.class
# => Process::Status

See Process::Status for more ways to handle exit status.

konsolebox
  • 72,135
  • 12
  • 99
  • 105
  • Nice. +1.......And if you don't like the perl-like variable names(who does?!!), you can write: `require 'english'` and `puts $CHILD_STATUS.exitstatus` – 7stud Aug 08 '14 at 17:09
1

I would have a look at the popen4 gem, as it will allow you to get stout/stderr and the exit status.

Also see this page describing 6 Ways to Run Shell Commands in Ruby which also details popen4 usage.

status = Open4::popen4("false") do |pid, stdin, stdout, stderr|
    puts "stdout: #{stdout}" 
end
puts status
briantist
  • 45,546
  • 6
  • 82
  • 127
  • why would I use a gem, isn't there a standard way in Ruby? – Incerteza Aug 06 '14 at 03:26
  • 1
    The second link I posted, describes 5 ways that are standard in Ruby. The 6th way, using the gem, is the only one that gets you both the status and the output, and does it quite nicely I think. – briantist Aug 06 '14 at 03:28
  • *The 6th way, using the gem, is the only one that gets you both the status and the output*...Not true. – 7stud Aug 06 '14 at 03:37
  • 1
    As shown by the other answers here, you are correct. I have definitely learned something myself! – briantist Aug 06 '14 at 03:41
0
#my_prog.rb
puts 'hello'
exit 10

...

require 'open3'

Open3.popen2("ruby my_prog.rb") do |stdin, stdout, wait_thr|
  puts stdout.read
  status_obj = wait_thr.value # Process::Status object returned.
  puts status_obj.exitstatus
end


--output:--
hello
10
7stud
  • 46,922
  • 14
  • 101
  • 127
0

I want exception as an instance of class Exception to be able to re-raise it, not just a string.

You can do this:

#my_prog.rb

puts 'hello'
10/0

...

require 'open3'

begin
  Open3.popen3("ruby my_prog.rb") do |stdin, stdout, stderr, wait_thr|
    puts stdout.read
    status_obj = wait_thr.value  #Process::Status object returned.
    puts status_obj.exitstatus

    error = stderr.read
    md = error.match(/\w+ Error/xm)

    if md
      e = Exception.const_get(md[0]).new if md 
      p e.class.ancestors
      raise e
    end

  end
rescue ZeroDivisionError
  puts 'Hey, someone divided by zero!'
end

--output:--
hello
1
[ZeroDivisionError, StandardError, Exception, Object, Kernel, BasicObject]
Hey, someone divided by zero!

Unfortunately, there are several exceptions whose names do not end in 'Error'--but you can modify the code to search for those specifically in the error message.

7stud
  • 46,922
  • 14
  • 101
  • 127
-1

Here is a way to get this out.

#!/usr/bin/env ruby

require 'open3'

Open3.popen3("ls -l") do |stdin, stdout, stderr, wait_thr|
  puts stdout.read
  puts wait_thr.value.exitstatus 
end

# >> total 52
# >> -rw-r--r-- 1 arup users    0 Aug  5 09:32 a.rb
# >> drwxr-xr-x 2 arup users 4096 Jul 20 20:37 FSS
# >> drwxr-xr-x 2 arup users 4096 Jul 20 20:37 fss_dir
# >> -rw-r--r-- 1 arup users   42 Jul 19 01:36 out.txt
# .....
#...
# >> 0

Doco is very clear of ::popen3. stdout gives IO object. So, we need to use IO#read method. And wait_thr.value gives us Process::Status. Now once we have that object, we can use #exitstatus method for the same.

Arup Rakshit
  • 116,827
  • 30
  • 260
  • 317