Intercepting STDERR

I'm writing a Ruby script to run Lua code for TextMate. Using:
output = `lua #{filename}`
works when the file prints happily, but when an error occurs (e.g. a
syntax error in the file) the error message is printed to stderr (I
think) and 'escapes' my capture.

How/can I catch output to stderr from another system command? (I want
to use the output later, I just don't want it spat out at that point.)

You could do:

output = `command 2>&1`

However, now $stderr is mixed in with $stdout.

-- Daniel

···

On Mar 1, 2006, at 6:43 PM, Phrogz wrote:

How/can I catch output to stderr from another system command? (I want
to use the output later, I just don't want it spat out at that point.)

harp:~ > gem install session >/dev/null 2>&1 && echo 'success'
   success

   harp:~ > cat a.rb
   require 'rubygems'
   require 'session'
   sh = Session::new
   command = %Q( ruby -e" STDERR.puts :stderr; STDOUT.puts :stdout " )
   stdout, stderr = sh.execute command

   require 'yaml'
   y "stdout" => stdout
   y "stderr" => stderr
   y "sh.status" => sh.status

   harp:~ > ruby a.rb

···

On Thu, 2 Mar 2006, Phrogz wrote:

I'm writing a Ruby script to run Lua code for TextMate. Using:
output = `lua #{filename}`
works when the file prints happily, but when an error occurs (e.g. a
syntax error in the file) the error message is printed to stderr (I
think) and 'escapes' my capture.

How/can I catch output to stderr from another system command? (I want
to use the output later, I just don't want it spat out at that point.)

   ---
   stdout: |
     stdout

   ---
   stderr: |
     stderr

   ---
   sh.status: 0

-a

--
judge your success by what you had to give up in order to get it.
- h.h. the 14th dali lama

That'll do for now, thanks! :slight_smile:

That's awesome, ara. Unfortunately, I need to write something that will
work on random MacOS machines witj a default Ruby install. Can I poke
about in the innards of session and steal ideas/code?

You may want to check out IO.popen and IO.popen3 (or is it popen2? I can never remembr the numbers)

···

On Mar 1, 2006, at 1:58 PM, Phrogz wrote:

That'll do for now, thanks! :slight_smile:

absolutely. basically what you're after is open3 - it's in the stdlib and
will probably suffice. session is overkill if you're not running multiple
commands per session (no pun intended) anyhow.

anyhow, here's the essence:

     harp:~ > cat a.rb
     def spawn command, opts = {}
       require 'open3'
       stdin = opts.values_at(:stdin, 'stdin', 0).compact.first
       stdout = opts.values_at(:stdout, 'stdout', 1).compact.first
       stderr = opts.values_at(:stderr, 'stderr', 2).compact.first

       Open3::popen3(command) do |i,o,e|
         i << stdin if stdin
         i.close # important!
         o.each{|line| stdout << line} if stdout
         e.each{|line| stderr << wrine} if stderr
       end

       $?.exitstatus
     end

     stdout, stderr = '', ''
     exitstatus = spawn 'cat', 0=>42, 1=>stdout, 2=>stderr

     require 'yaml'
     y 'exitstatus' => exitstatus,
       'stdout' => stdout,
       'stderr' => stderr

     harp:~ > ruby a.rb

···

On Thu, 2 Mar 2006, Phrogz wrote:

That's awesome, ara. Unfortunately, I need to write something that will
work on random MacOS machines witj a default Ruby install. Can I poke
about in the innards of session and steal ideas/code?

     ---
     stdout: "42"
     stderr: ""
     exitstatus: 0

regards.

-a

--
judge your success by what you had to give up in order to get it.
- h.h. the 14th dali lama

Thanks so much, Ara. That's almost perfect, except that the exitstatus
seems to be eaten. No matter what happens, I get a clean 0 exitstatus.
For example:

require 'open3'

`lua xxx`
#=> lua: cannot open xxx: No such file or directory

p $?.exitstatus
#=> 1

output, errors = '', ''
p Open3::popen3( "lua xxx" ){ |i,o,e|
  i.close
  o.each { |line| output << line }
  e.each { |line| errors << line }
  $?.exitstatus
}
#=> 0

I can mostly infer the failure based on whether or not messages came to
stderr, but it'd be nice to know for sure.

right you are. if you check out ruby-1.8.4/lib/open3.rb you'll see :

...
     pid = fork{
       # child
       fork{
   # grandchild
         ...
       }
       exit!(0) # check this out
     }
...

so you'll never see an error. my open 4 package fixes this - i actually just
made a release a few weeks ago to support throwing and exception when the exec
fails. here's an example (it's on rubyforge):

     harp:~ > cat a.rb
     IO::popen("gem install open4 -r -y"){|pipe| pipe.read} and puts "installed open4..."
     require "rubygems"
     require "open4"

     def spawn cmd, opts = {}
       getopt = lambda{|o,*d| opts[o] || opts[o.to_s] || opts[o.to_s.intern] || d.shift}
       stdin = getopt["stdin"] || getopt[0, ""]
       stdout = getopt["stdout"] || getopt[1, ""]
       stderr = getopt["stderr"] || getopt[2, ""]

       Open4::popen4(cmd) do |cid,i,o,e|
         stdin.each{|line| i << line}
         i.close
         o.each{|line| stdout << line }
         e.each{|line| stderr << line }
       end

       [$?.exitstatus, stdout, stderr]
     end

     bt =
       lambda{|e| STDERR.puts %Q[#{ e.message } (#{ e.class })\n#{ e.backtrace.join "\n" }\n]}

     argvs =
       ["fubar"],
       ["ruby does-not-exist"],
       ["ruby", {"stdin" => "p 42"}]

     argvs.each do |argv|
       cmd, opts = argv
       puts
       puts "--- spawn( #{ cmd.inspect }, #{ opts.inspect } ) ---"
       p( spawn(*argv) ) rescue bt[$!]
       puts
     end

     harp:~ > ruby a.rb
     installed open4...

     --- spawn( "fubar", nil ) ---
     No such file or directory - fubar (Errno::ENOENT)
     /home/ahoward//lib/ruby/site_ruby/1.8/open4.rb:75:in `exec'
     /home/ahoward//lib/ruby/site_ruby/1.8/open4.rb:75:in `popen4'
     /home/ahoward//lib/ruby/site_ruby/1.8/open4.rb:57:in `popen4'
     a.rb:11:in `spawn'
     a.rb:36
     a.rb:32

     --- spawn( "ruby does-not-exist", nil ) ---
     [1, "", "ruby: No such file or directory -- does-not-exist (LoadError)\n"]

     --- spawn( "ruby", {"stdin"=>"p 42"} ) ---
     [0, "42\n", ""]

hth.

i'd sure like to get this in the stdlib...

-a

···

On Sat, 4 Mar 2006, Phrogz wrote:

Thanks so much, Ara. That's almost perfect, except that the exitstatus
seems to be eaten. No matter what happens, I get a clean 0 exitstatus.
For example:

require 'open3'

`lua xxx`
#=> lua: cannot open xxx: No such file or directory

p $?.exitstatus
#=> 1

output, errors = '', ''
p Open3::popen3( "lua xxx" ){ |i,o,e|
  i.close
  o.each { |line| output << line }
  e.each { |line| errors << line }
  $?.exitstatus
}
#=> 0

I can mostly infer the failure based on whether or not messages came to
stderr, but it'd be nice to know for sure.

--
judge your success by what you had to give up in order to get it.
- h.h. the 14th dali lama