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.
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
|
commands with the three aforementioned pipes.