我正在围绕CLI制作一个Ruby包装器。我找到了一个简洁的方法,Open3.capture3
(内部使用Open3.popen3
(,它让我执行命令并捕获标准输出、标准和退出代码。
我想检测的一件事是是否找不到 CLI 可执行文件(并为此引发特殊错误(。我知道 UNIX 外壳在找不到命令时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