问题
I'm making a Ruby wrapper around a CLI. And I found a neat method, Open3.capture3
(which internally uses Open3.popen3
), which lets me execute commands and captures stdout, stderr and exit code.
One thing that I want to detect is if the CLI executable wasn't found (and raise a special error for that). I know that the UNIX shell gives exit code 127
when command wasn't 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 that in mind, I wrote 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 ran 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 does this error occur? I thought Open3.capture3
was supposed to execute that command directly in the shell, why then don't I get a normal STDERR and exit code of 127
?
回答1:
Open3.popen3
delegates to Kernel.spawn
, which depending on the way the command is passed in, gives the command to shell or directly to 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 Kernel.spawn("foo")
, it would be passed to the shell (and not OS). But it doesn't, 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).
Last paragraph reveals the solution.
require "open3"
stdout, stderr, status = Open3.capture3(command + ";")
case status.exitstatus
when 0
return stdout
when 1, 127
raise MyError, stderr
end
来源:https://stackoverflow.com/questions/26040249/why-does-open3-popen3-return-wrong-error-when-executable-is-missing