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.)

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.)

You could do:

output = command 2>&1

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

– Daniel

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.)

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

stdout: |
stdout


stderr: |
stderr


sh.status: 0

-a

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

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

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

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

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?

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?

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
 ---
 stdout: "42"
 stderr: ""
 exitstatus: 0

regards.

-a

On Sat, 4 Mar 2006, Phrogz wrote:

#=> 1

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
   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

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.