为什么Open3.popen3在缺少可执行文件时返回错误的错误?

我正在围绕CLI创建一个Ruby包装器。 我找到了一个简洁的方法, Open3.capture3 (内部使用Open3.popen3 ),它允许我执行命令并捕获stdout,stderr和退出代码。

我想要检测的一件事是,是否找不到CLI可执行文件(并为此引发特殊错误)。 我知道UNIX shell在找不到命令时给出退出代码127 。 当我在bash中执行$ foo时,我得到-bash: foo: command not found ,这正是我要显示的错误消息。

考虑到这一切,我写了这样的代码:

 require "open3" stdout, stderr, status = Open3.capture3(command) case status.exitstatus when 0 return stdout when 1, 127 raise MyError, stderr end 

但是,当我使用command = "foo"运行它时,我收到一个错误:

 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' 

为什么会出现此错误? 我以为Open3.capture3应该直接在shell中执行该命令,为什么我不能得到正常的STDERR并退出127代码?

Open3.popen3委托给Kernel.spawn ,它取决于传入命令的方式,将命令提供给shell或直接提供给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) 

我们可能期望如果我们调用Kernel.spawn("foo") ,它将被传递给shell(而不是OS)。 但事实并非如此, Kernel.exec文档解释了原因:

 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). 

最后一段揭示了解决方案。

 require "open3" stdout, stderr, status = Open3.capture3(command + ";") case status.exitstatus when 0 return stdout when 1, 127 raise MyError, stderr end