Capturing "command not found" errors from Ruby's backticks? - ruby

Is there a way to capture a "command not found" error in a Ruby script? For instance, given:
output = `foo`
How do I trap the situation where foo isn't installed? I expected that I could rescue an exception, but this doesn't seem to work on 1.8.7. Is there a different way of calling the subprocess that will do what I want? Or is there a different approach?
Update
My apologies, I forgot to mention a hidden requirement: I would prefer that the interpreter doesn't leak the command line to the user (it can contain sensitive data), hence why the exception catching method is preferred. Apologies again for leaving this out the first time.

Use the return code!
irb(main):001:0> `date`
=> "Mo 24. Jan 16:07:15 CET 2011\n"
irb(main):002:0> $?
=> #<Process::Status: pid=11556,exited(0)>
irb(main):003:0> $?.to_i
=> 0
irb(main):004:0> `foo`
(irb):4: command not found: foo
=> ""
irb(main):005:0> $?.to_i
=> 32512
http://corelib.rubyonrails.org/classes/Process/Status.html
Redirecting STDERR to STDOUT will give you the output as return value instead of bloating it just out:
irb(main):010:0> `foo 2>&1`
=> "sh: foo: not found\n"
irb(main):011:0> $?.to_i
=> 32512

Related

Is there a named method equivalent to $? in Ruby?

In Ruby, I'd like to access the status of the most recently executed external command's result information, that is, the information provided by $?, but would prefer a named method or variable. Is there such a thing, and, if so, how do I access it?
Process.last_status returns the same Process::Status instance as $?:
$ irb
> `ls 12341234`
ls: 12341234: No such file or directory
=> ""
> $? == Process.last_status
=> true
> $?.equal? Process.last_status
=> true
> $?.class
=> Process::Status
Thread Local
Also, this variable is thread local, so each thread will have its own variable (i.e. threads will not overwrite a single global value). From the docs at https://ruby-doc.org/core-2.5.0/Process.html#method-c-last_status:
"Returns the status of the last executed child process in the current thread."
would prefer a named […] variable.
The English standard library provides descriptive aliases for $? and other special global variables. But unlike Process.last_status this library has be loaded before any of its aliases can be used. The upside is it exists in Ruby < 2.5.
$ irb -rEnglish
> `ls 12341234`
ls: 12341234: No such file or directory
=> ""
> $? == CHILD_STATUS
=> true
> $?.equal? CHILD_STATUS
=> true
> $?.class
=> Process::Status

Open3.popen3 returns wrong error Errno::ENOENT on Windows

I have the following code in test.rb:
require 'open3'
cmd = 'C:\Program Files\foo\bar.exe'
Open3.popen3(cmd) do |stdin, stdout, stderr, wait_thr|
puts "stdout: #{stdout.read}"
puts "\n\n"
puts "stderr: #{stderr.read}"
end
bar.exe is a console application that I created, located in C:\Program Files\foo\. When I run bar.exe:
it outputs "Hello world!"
with any argument, like bar.exe /blah, it outputs a help message.
When I run ruby test.rb I get this error:
C:\RailsInstaller/Ruby2.2.0/lib/ruby/2.2.0/open3.rb:193:in 'spawn': No such file or directory - C:\Program Files\foo\bar.exe (Errno::ENOENT)
from C:\RailsInstaller/Ruby2.2.0/lib/ruby/2.2.0/open3.rb:193:in 'popen_run'
from C:\RailsInstaller/Ruby2.2.0/lib/ruby/2.2.0/open3.rb:193:in 'popen3'
from test.rb:3:in '<main>'
If I change the code to call popen3:
Open3.popen3(cmd, '')
I don't get the Errno::ENOENT error, instead I get the help message, but I want the "Hello World" output.
I searched for a solution but nothing is working, including the answer to "Why does Open3.popen3 return wrong error when executable is missing?".
Why am I getting this error and how do I solve it?
Meditate on this:
cmd = "\P\f\b"
cmd.size # => 3
cmd.chars # => ["P", "\f", "\b"]
cmd.chars.map(&:ord) # => [80, 12, 8]
cmd = "\\P\\f\\b"
cmd.size # => 6
cmd.chars # => ["\\", "P", "\\", "f", "\\", "b"]
cmd.chars.map(&:ord) # => [92, 80, 92, 102, 92, 98]
cmd = '\P\f\b'
cmd.size # => 6
cmd.chars # => ["\\", "P", "\\", "f", "\\", "b"]
cmd.chars.map(&:ord) # => [92, 80, 92, 102, 92, 98]
You're using a double-quoted string with single backslashes as path/directory separators as in the first example. The single back-slashed \f and \b are escaped characters in a double-quoted string, and are not recognized as they were typed using \ f or \ b.
You have two ways of dealing with this, either escaping the backslashes as in the second example, or by using a single-quoted string, as in the third example. It's considered messy to use the second means so use the last for readability and easier maintenance. You get the same characters with less visual noise. This is applicable to string use in most langauges.
The second thing to know is that Ruby doesn't need reverse-slashes as path delimiters. The IO documentation says:
Ruby will convert pathnames between different operating system conventions if possible. For instance, on a Windows system the filename "/gumby/ruby/test.rb" will be opened as "\gumby\ruby\test.rb". When specifying a Windows-style filename in a Ruby string, remember to escape the backslashes:
"c:\\gumby\\ruby\\test.rb"
Our examples here will use the Unix-style forward slashes; File::ALT_SEPARATOR can be used to get the platform-specific separator character.
Finally, you should look at Ruby's Shell and Shellwords in the STDLib. They're your friends.
You are having trouble because "Program Files" is a folder with a space in it. Whenever that happens, you need to double quote it, just as you would on a cmd.exe prompt. And when you're double-quoting, you must remember that your backslash character "\" is an escape character, so you have to double-backslash to get the proper folder separators for Windows. I'm going to use code which actually returns something in my environment; adjust it to your taste. So your code should look like:
require 'open3'
cmd = "\"C:\\Program Files\\Git\\bin\\git.exe\""
Open3.popen3(cmd) do |stdin, stdout, stderr, wait_thr|
puts "stdout: #{stdout.read}"
puts "\n\n"
puts "stderr: #{stderr.read}"
end
If you have command line parameters to pass to git, you'd do it like this:
require 'open3'
cmd = "\"C:\\Program Files\\Git\\bin\\git.exe\" --version"
Open3.popen3(cmd) do |stdin, stdout, stderr, wait_thr|
puts "stdout: #{stdout.read}"
puts "\n\n"
puts "stderr: #{stderr.read}"
end
Use Open3.popen3([bin, bin]) to prevent shell command handling for a single argument to popen3 (and related methods like spawn).
As you noticed, multiple args can be passed normally without invoking a shell (e.g.
Open3.popen3(bin, arg1, arg2)).

Ruby One liner | test remote host port

I'm trying below Perl command in Ruby
Perl
perl -MIO::Socket::INET -e 'until(new IO::Socket::INET("localhost:80")) { print "Waiting for network..\n"; sleep 1}'
How do I same thing in ruby ?
I have tried :
require 'socket'
until !( TCPSocket.new("localhost",80).close ) do
puts "Wait..."
sleep 1
end
I'm looking for one liner in Ruby.
The main difference is that Ruby will raise an error if it cannot establish the IO, so you need to rescue the error condition. It changes the flow somewhat, but is still very do-able:
loop { break if (TCPSocket.open("localhost",80) rescue nil); puts "Wait...."; sleep 1 }
As seen from other answer, it is possible to make a more literal conversion from the Perl version. Just use the Ruby expression (TCPSocket.open("localhost",80) rescue nil) to replace Perl's new IO::Socket::INET("localhost:80") so that Ruby's raise an error behaviour better matches Perl's return undef when cannot create the object.
This is similar:
require 'socket'
(puts "Waiting..."; sleep 1) until (TCPSocket.open("localhost",3000) rescue nil)
Full command line:
ruby -r socket -e '(puts "Waiting..."; sleep 1) until (TCPSocket.open("localhost",3000) rescue nil)'

ruby system command check exit code

I have a bunch of system calls in ruby such as the following and I want to check their exit codes simultaneously so that my script exits out if that command fails.
system("VBoxManage createvm --name test1")
system("ruby test.rb")
I want something like
system("VBoxManage createvm --name test1", 0) <-- where the second parameter checks the exit code and confirms that that system call was successful, and if not, it'll raise an error or do something of that sort.
Is that possible at all?
I've tried something along the lines of this and that didn't work either.
system("ruby test.rb")
system("echo $?")
or
`ruby test.rb`
exit_code = `echo $?`
if exit_code != 0
raise 'Exit code is not zero'
end
From the documentation:
system returns true if the command gives zero exit status, false for
non zero exit status. Returns nil if command execution fails.
system("unknown command") #=> nil
system("echo foo") #=> true
system("echo foo | grep bar") #=> false
Furthermore
An error status is available in $?.
system("VBoxManage createvm --invalid-option")
$? #=> #<Process::Status: pid 9926 exit 2>
$?.exitstatus #=> 2
For me, I preferred use `` to call the shell commands and check $? to get process status. The $? is a process status object, you can get the command's process information from this object, including: status code, execution status, pid, etc.
Some useful methods of the $? object:
$?.exitstatus => return error code
$?.success? => return true if error code is 0, otherwise false
$?.pid => created process pid
system returns false if the command has an non-zero exit code, or nil if there is no command.
Therefore
system( "foo" ) or exit
or
system( "foo" ) or raise "Something went wrong with foo"
should work, and are reasonably concise.
You're not capturing the result of your system call, which is where the result code is returned:
exit_code = system("ruby test.rb")
Remember each system call or equivalent, which includes the backtick-method, spawns a new shell, so it's not possible to capture the result of a previous shell's environment. In this case exit_code is true if everything worked out, nil otherwise.
The popen3 command provides more low-level detail.
One way to do this is to chain them using and or &&:
system("VBoxManage createvm --name test1") and system("ruby test.rb")
The second call won't be run if the first fails.
You can wrap those in an if () to give you some flow-control:
if (
system("VBoxManage createvm --name test1") &&
system("ruby test.rb")
)
# do something
else
# do something with $?
end
Ruby 2.6 added option to raise exception in Kernel#system:
system("command", exception: true)
I want something like
system("VBoxManage createvm --name test1", 0) <-- where the second parameter checks the exit code and confirms that that system call was successful, and if not, it'll raise an error or do something of that sort.
You can add exception: true to your system call to have an error raised on non 0 exit codes.
For example, consider this small wrapper around system which prints the command (similar to bash -x, fails if there's a non 0 exit code (like bash -e) and returns the actual exit code:
def sys(cmd, *args, **kwargs)
puts("\e[1m\e[33m#{cmd} #{args}\e[0m\e[22m")
system(cmd, *args, exception: true, **kwargs)
return $?.exitstatus
end
To be called like: sys("hg", "update")
If you want to call a program that uses a different convention for exit codes, you can suppress raising the exception:
sys("robocopy", src, dst, "/COPYALL", "/E", "/R:0", "/DCOPY:T", exception: false)
You can also suppress stdout and stderr for noisy programs:
sys("hg", "update", "default", :out => File::NULL, :err => File::NULL)

Ruby system call get information about command failure

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

Resources