I'd like to know how to catch exceptions from Open3.popen3 on Ruby.
require "open3"
begin
Open3.popen3("./somebinary") {|i,o,e,t|
# IO.popen("./somebinary") {|i|
$stderr.puts "popen"
i.puts "some string"
}
rescue StandardError => e
$stderr.puts e.message
$stderr.puts e.backtrace.inspect
$stderr.puts "error"
exit(1)
end
The problem is that the binary executable is of a wrong architecture and doesn't run on the local machine. When invoked from the command line, like so ./somebinary, you see the error message.
With popen, the error is caught by the rescue clause, but not with popen3.
If the rescue clause didn't have StandardError => e, the clause is entered, but you can't know the reason for the error because all you see is the message "error" from $stderr.puts "error". With popen3, the stderr from the command line vanishes!
I'm using ruby 3.0.1p64 on macOS 11.3.1 .
A complete example
~/tmp $ ./somebinary
dyld: Library not loaded: /usr/local/opt/gcc/lib/gcc/10/libgfortran.5.dylib
Referenced from: /Users/furue/tmp/./somebinary
Reason: image not found
fish: Job 1, './somebinary' terminated by signal SIGABRT (Abort)
~/tmp [134] $ ruby --version
ruby 3.0.1p64 (2021-04-05 revision 0fb782ee38) [arm64-darwin20]
~/tmp $ cat try-popen3.rb
require "open3"
begin
Open3.popen3("./somebinary") {|i,o,e,t|
$stderr.puts "in popen . . . "
i.puts "some string"
}
rescue
$stderr.puts "error in rescue . . . "
exit(1)
end
~/tmp $ ruby try-popen3.rb
in popen . . .
~/tmp $ cat try-popen3-standarderror.rb
require "open3"
begin
Open3.popen3("./somebinary") {|i,o,e,t|
$stderr.puts "in popen . . . "
i.puts "some string"
}
rescue StandardError => e
$stderr.puts e.message
$stderr.puts e.backtrace.inspect
$stderr.puts "error in rescue . . . "
exit(1)
end
~/tmp $ ruby try-popen3-standarderror.rb
in popen . . .
~/tmp $ cat try-popen.rb
require "open3"
begin
IO.popen("./somebinary") {|i|
$stderr.puts "in popen . . . "
i.puts "some string"
}
rescue
$stderr.puts "error in rescue . . . "
exit(1)
end
~/tmp $ ruby try-popen.rb
in popen . . .
dyld: Library not loaded: /usr/local/opt/gcc/lib/gcc/10/libgfortran.5.dylib
Referenced from: /Users/furue/tmp/./somebinary
Reason: image not found
error in rescue . . .
~/tmp [1] $ cat try-popen-standarderror.rb
require "open3"
begin
IO.popen("./somebinary") {|i|
$stderr.puts "in popen . . . "
i.puts "some string"
}
rescue StandardError => e
$stderr.puts e.message
$stderr.puts e.backtrace.inspect
$stderr.puts "error in rescue . . . "
exit(1)
end
~/tmp $ ruby try-popen-standarderror.rb
in popen . . .
dyld: Library not loaded: /usr/local/opt/gcc/lib/gcc/10/libgfortran.5.dylib
Referenced from: /Users/furue/tmp/./somebinary
Reason: image not found
not opened for writing
["try-popen-standarderror.rb:5:in `write'", "try-popen-standarderror.rb:5:in `puts'", "try-popen-standarderror.rb:5:in `block in <main>'", "try-popen-standarderror.rb:3:in `popen'", "try-popen-standarderror.rb:3:in `<main>'"]
error in rescue . . .
~/tmp [1] $
Related
For a specific bash command when I execute it locally after completion I got the shell free but when I execute remotely the shell hangs like that:
[user#host ~]$ ruby bin/remote_control.rb server start_server1
Running /home/server_manager.sh start_server ... wait
[]
When I use ruby and NET::SSH to call remotely this command and it's necessary to press ctrl+C to to get the shell prompt available again and press enter doesn't work.
Again the remote script/command /home/server_manager.sh doesn't has this behavior when
called locally
For get free the terminal the script has this syntax:
I'm trying to execute in backgroud
`commmand &` 2>&1 | echo "\n"
And the ruby code bellow is used for calling the script above:
Net::SSH.start(#hostname, #username, :password => #password) do |ssh|
channel = ssh.open_channel do |ch|
ch.exec #cmd do |ch, success|
raise "Could not execute command: #{cmd}" unless success
ch.on_data do |c, data|
begin
if !data.nil? then
print data
else
exit
end
rescue SystemExit
puts "Rescued a SystemExit exception"
end
end
ch.on_extended_data do |c, type, data|
begin
if !data.nil? then
print data
else
exit
end
rescue SystemExit
puts "Rescued a SystemExit exception"
end
end
ch.on_eof do |ch|
puts "Cmd finished with success: #{#cmd}"
$LOG.info("Cmd finished with success: #{#cmd}")
end
ch.on_close { puts "Done!" }
end
end
channel.wait
ssh.loop
end
But I haven't success until now. What I need to add to this code to always have the shell free.
I'm having problems getting Net::SSH::Multi library to work, it should connect to each box and run that command, I'm trying to get the output.
Here's my code:
#!/usr/bin/env ruby
require 'rubygems'
require 'net/ssh'
require 'net/ssh/multi'
#ssh_responses = Array.new
puts "Starting vhost grab..."
# SSH into each instance, and download Vhost Dump
Net::SSH::Multi.start(:on_error => :ignore) do |ssh|
ssh.use 'ec2-1-1-1-1.compute-1.amazonaws.com', :user => 'portal_user', :keys => ['~/.ssh/portal_user.pem']
ssh.use 'ec2-1-1-1-2.compute-1.amazonaws.com', :user => 'portal_user', :keys => ['~/.ssh/portal_user.pem']
puts "Added servers..."
stdout = ""
# run this on all boxes
ssh.exec 'pwd' do |channel, stream, data|
puts "Trying execution..."
channel.request_pty do |c, success|
puts "Trying to request tty..."
if success
puts "Successfully requested tty"
command = "sudo ls /root"
c.exec(command) do |c, success, data|
puts "Executing commands..."
stdout << data if stream == :stdout
end
#ssh_responses << stdout
puts "Storing responses..."
end
end
end
ssh.loop
end
puts #ssh_responses
puts "Script executed."
And the response I get from running is:
$ ruby serverAction.rb
Starting vhost grab...
Added servers...
Trying execution...
Script executed.
require 'optparse'
params = ARGV.getopts("a:", "AA")
ruby a.rb -a shows:
a.rb:3:in `<main>': missing argument: -a (OptionParser::MissingArgument)
ruby a.rb -b shows:
a.rb:3:in `<main>': invalid option: -b (OptionParser::InvalidOption)
I want to show my help message, how do I do that?
Actually you can use on_tail inside the block of OptionParser.new.
But since you're simply trying with ARGV.getopts, a hack to show your own help message would be rescuing exceptions:
require 'optparse'
help_msg = <<EOM
This is help message:
Hello buddy, you may do something wrong
...
EOM
begin
params = ARGV.getopts("a:", "AA")
rescue => e
puts e.message
puts '=' * 80
puts help_msg
end
Output:
ruby a.rb -a
#=>
missing argument: -a
================================================================================
This is help message:
Hello buddy, you may do something wrong
...
ruby a.rb -b
#=>
invalid option: -b
================================================================================
This is help message:
Hello buddy, you may do something wrong
...
Say I have a simple command line interpreter like this:
while true
print '> '
cmd = gets.chomp
break if cmd =~ /^(exit|quit)/
system(cmd) || puts('Command not found or invalid.')
end
I would like to, instead of the "Command not found or invalid." message get an actual error message, like one you would get from bash. How would I do this?
well, if it's unix-like system you could actually append 2>&1 to your command:
system(cmd + ' 2>&1 ')
which would redirect your stderr to stdout
another way is using %x[...] :
irb(main):027:0> def hello
irb(main):029:2* %x[hello]
irb(main):030:2> rescue Exception => e
irb(main):031:2> puts e.message
irb(main):033:1> end
=> nil
irb(main):034:0> hello
No such file or directory - hello
=> nil
irb(main):035:0>
meaning, you can rescue the command execution and return the exception message
How do I redirect stderr and stdout to file for a Ruby script?
From within a Ruby script, you can redirect stdout and stderr with the IO#reopen method.
# a.rb
$stdout.reopen("out.txt", "w")
$stderr.reopen("err.txt", "w")
puts 'normal output'
warn 'something to stderr'
$ ls
a.rb
$ ruby a.rb
$ ls
a.rb err.txt out.txt
$ cat err.txt
something to stderr
$ cat out.txt
normal output
Note: reopening of the standard streams to /dev/null is a good old method of helping a process to become a daemon. For example:
# daemon.rb
$stdout.reopen("/dev/null", "w")
$stderr.reopen("/dev/null", "w")
def silence_stdout
$stdout = File.new( '/dev/null', 'w' )
yield
ensure
$stdout = STDOUT
end
./yourscript.rb 2>&1 > log.txt
will redirect stdout and stderr to the same file.
A full example with $stdout and $stderr redirected to a file and how to restore the initial behavior.
#!/usr/bin/ruby
logfile = "/tmp/testruby.log"
#original_stdout = $stderr.dup
#original_stderr = $stderr.dup
$stdout.reopen(logfile, "w")
$stdout.sync = true
$stderr.reopen($stdout)
def restore_stdout
$stdout.reopen(#original_stdout)
$stderr.reopen(#original_stderr)
end
def fail_exit(msg)
puts "- #{msg}" # to the logfile
restore_stdout
$stderr.puts "+ #{msg}" # to standard error
exit!
end
def success_exit(msg)
puts "- #{msg}" # to the logfile
restore_stdout
$stdout.puts "+ #{msg}" # to standard output
exit
end
puts "This message goes to the file"
success_exit "A successful exit message"