Why does Open3.popen3 return the wrong error when there is no executable file?
I am making a Ruby wrapper around the CLI. And I found a neat method Open3.capture3
(which internally uses Open3.popen3
) that allows me to execute commands and grab stdout, stderr and exit code.
One thing I want to detect is if the CLI executable is not found (and a special error is thrown for it). I know that UNIX shell gives exit code 127
when command was not found. And when I execute $ foo
in bash I get -bash: foo: command not found
which is exactly the error message I want to display.
With all this in mind, I wrote the code like this:
require "open3"
stdout, stderr, status = Open3.capture3(command)
case status.exitstatus
when 0
return stdout
when 1, 127
raise MyError, stderr
end
But, when I run it with command = "foo"
, I get an error:
Errno::ENOENT: No such file or directory - foo
/Users/janko/.rbenv/versions/2.1.3/lib/ruby/2.1.0/open3.rb:193:in `spawn'
/Users/janko/.rbenv/versions/2.1.3/lib/ruby/2.1.0/open3.rb:193:in `popen_run'
/Users/janko/.rbenv/versions/2.1.3/lib/ruby/2.1.0/open3.rb:93:in `popen3'
/Users/janko/.rbenv/versions/2.1.3/lib/ruby/2.1.0/open3.rb:252:in `capture3'
Why is this error occurring? I thought I Open3.capture3
had to execute this command directly in the shell, why don't I get a normal STDERR code and exit code 127
?
source to share
Open3.popen3
delegates Kernel.spawn
, which, depending on how the command is passed, pass the command to the shell or directly to the OS.
commandline : command line string which is passed to the standard shell
cmdname, arg1, ... : command name and one or more arguments (This form does not use the shell. See below for caveats.)
[cmdname, argv0], arg1, ... : command name, argv[0] and zero or more arguments (no shell)
We might expect that if we call it Kernel.spawn("foo")
, it will be passed to the shell (not the OS). But this is not the case, the documentation for Kernel.exec
explains why:
If the string from the first form (exec("command")) follows these simple rules:
* no meta characters
* no shell reserved word and no special built-in
* Ruby invokes the command directly without shell
You can force shell invocation by adding ";" to the string (because ";" is a meta character).
The last paragraph shows the solution.
require "open3"
stdout, stderr, status = Open3.capture3(command + ";")
case status.exitstatus
when 0
return stdout
when 1, 127
raise MyError, stderr
end
source to share