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


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

1 answer


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




All Articles