3

我正在围绕 CLI 制作一个 Ruby 包装器。我找到了一个简洁的方法Open3.capture3(内部使用Open3.popen3),它可以让我执行命令并捕获标准输出、标准错误和退出代码。

我想要检测的一件事是是否找不到 CLI 可执行文件(并为此引发特殊错误)。我知道 UNIX shell127在找不到命令时会给出退出代码。当我$ foo在 bash 中执行时,我得到-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

4

1 回答 1

9

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
于 2014-09-25T14:20:55.477 回答