Using open3 in ruby to call an interactive shell command with all three pipes while avoiding block

Apparently when using the three standard pipes (stdin, stdout, stderr)
in your own programming, it is REALLY easy to block or deadlock on
them. At first I figured there were problems in the ruby *open3 
library, but this is not ruby specific, I found message board
discussions about similar problems using perl open3 libraries.

Trying to figure out these quirks is made harder by the fact that all
of the common ruby examples are non-interactive, and just close the
stdin before trying to read any output. Even worse, many, many shell
programs don’t use stderr and stdout in what I understood as the
proper way. For instance I first as trying to use ftp as the command,
but this proved beyond my abilities. I then came up with a simple
working example that calls dc (the simple reverse polish calculator
available on most unixes), and discovered that on errors it outputs to
both stderr and stdout (different text). I was not able to in any way
to get the output from stderr until I closed stdin (short of
redirecting stderr to a temp file).

This drives home the point that you shouldn’t be doing this too much,
in ruby. Generally, use a better, task-specific ruby library, for
these sort of tasks. If you want to use ftp, use net-ftp, for
instance. If you want to use more extensive interactive shell
sessions, check out Ara Howard’s session
gem
 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 
#http://github.com/ahoward/open4 is an alternative that handles pid and exit status
#http://github.com/ahoward/session a more fleshed our shell utility
require ‘open3’
 
Open3.popen3(“dc”) do |stdin, stdout, stderr|
  t = Thread.new(stderr) do |terr|
    while (line = terr.gets)
      puts “stderr: #{line}
    end
  end
  
  puts “pushing 5 to stack”
  stdin.puts(5)
  puts “pushing 10 to stack”
  stdin.puts(10)
  puts “pushing + to stack”
  stdin.puts(”+”)
  puts “sending print command”
  stdin.puts(“p”)
  result = stdout.gets
  puts “stdout: #{ result }
  
  puts \ntrying an unsupported command”
  stdin.puts(“b”)
 
 
  extra_info_not_in_stderror = stdout.gets
  puts “stdout: #{extra_info_not_in_stderror}
 
 
  puts \nclosing stdin to avoid deadlock with stderr”
  stdin.close
 
  # puts “let’s give stderr a chance to run”
  # t.run or sleep 1
  
  puts “joining”
  t.join
end

*Open3 as a naming convention tends to be a library to call shell
commands with the three aforementioned pipes.

Posted via email from a timocracy of one | Comment »