2

I have the following code in test.rb:

require 'open3'
cmd = 'C:\Program Files\foo\bar.exe'
Open3.popen3(cmd) do |stdin, stdout, stderr, wait_thr|
  puts "stdout: #{stdout.read}"
  puts "\n\n"
  puts "stderr: #{stderr.read}"
end

bar.exe is a console application that I created, located in C:\Program Files\foo\. When I run bar.exe:

  • it outputs "Hello world!"
  • with any argument, like bar.exe /blah, it outputs a help message.

When I run ruby test.rb I get this error:

C:\RailsInstaller/Ruby2.2.0/lib/ruby/2.2.0/open3.rb:193:in 'spawn': No such file or directory - C:\Program Files\foo\bar.exe (Errno::ENOENT)
from C:\RailsInstaller/Ruby2.2.0/lib/ruby/2.2.0/open3.rb:193:in 'popen_run'
from C:\RailsInstaller/Ruby2.2.0/lib/ruby/2.2.0/open3.rb:193:in 'popen3'
from test.rb:3:in '<main>'

If I change the code to call popen3:

Open3.popen3(cmd, '')

I don't get the Errno::ENOENT error, instead I get the help message, but I want the "Hello World" output.

I searched for a solution but nothing is working, including the answer to "Why does Open3.popen3 return wrong error when executable is missing?".

Why am I getting this error and how do I solve it?

Community
  • 1
  • 1
dhrubo_moy
  • 1,144
  • 13
  • 31

3 Answers3

1

Meditate on this:

cmd = "\P\f\b"
cmd.size             # => 3
cmd.chars            # => ["P", "\f", "\b"]
cmd.chars.map(&:ord) # => [80, 12, 8]

cmd = "\\P\\f\\b"
cmd.size             # => 6
cmd.chars            # => ["\\", "P", "\\", "f", "\\", "b"]
cmd.chars.map(&:ord) # => [92, 80, 92, 102, 92, 98]

cmd = '\P\f\b'
cmd.size             # => 6
cmd.chars            # => ["\\", "P", "\\", "f", "\\", "b"]
cmd.chars.map(&:ord) # => [92, 80, 92, 102, 92, 98]

You're using a double-quoted string with single backslashes as path/directory separators as in the first example. The single back-slashed \f and \b are escaped characters in a double-quoted string, and are not recognized as they were typed using \ f or \ b.

You have two ways of dealing with this, either escaping the backslashes as in the second example, or by using a single-quoted string, as in the third example. It's considered messy to use the second means so use the last for readability and easier maintenance. You get the same characters with less visual noise. This is applicable to string use in most langauges.

The second thing to know is that Ruby doesn't need reverse-slashes as path delimiters. The IO documentation says:

Ruby will convert pathnames between different operating system conventions if possible. For instance, on a Windows system the filename "/gumby/ruby/test.rb" will be opened as "\gumby\ruby\test.rb". When specifying a Windows-style filename in a Ruby string, remember to escape the backslashes:

"c:\\gumby\\ruby\\test.rb"

Our examples here will use the Unix-style forward slashes; File::ALT_SEPARATOR can be used to get the platform-specific separator character.

Finally, you should look at Ruby's Shell and Shellwords in the STDLib. They're your friends.

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
  • Thanks a lot for the detailed answer. I used single quotes first. But it didn't work. Strange behavior! Followed Jay Godse's suggestion and it worked. – dhrubo_moy Aug 19 '16 at 13:42
  • The advice to escape a space is valid. The advice to use escaped-backslashes only leads to noise. – the Tin Man Aug 20 '16 at 00:31
  • I completely agree. The strange thing is `Open3.popen3('C:\Program Files\foo\bar.exe')` gives an error while `Open3.popen3("\"C:\\Program Files\\foo\\bar.exe\"")` works fine. – dhrubo_moy Aug 20 '16 at 04:15
0

You are having trouble because "Program Files" is a folder with a space in it. Whenever that happens, you need to double quote it, just as you would on a cmd.exe prompt. And when you're double-quoting, you must remember that your backslash character "\" is an escape character, so you have to double-backslash to get the proper folder separators for Windows. I'm going to use code which actually returns something in my environment; adjust it to your taste. So your code should look like:

require 'open3'
cmd = "\"C:\\Program Files\\Git\\bin\\git.exe\""
Open3.popen3(cmd) do |stdin, stdout, stderr, wait_thr|
  puts "stdout: #{stdout.read}"
  puts "\n\n"
  puts "stderr: #{stderr.read}"
end

If you have command line parameters to pass to git, you'd do it like this:

require 'open3'
cmd = "\"C:\\Program Files\\Git\\bin\\git.exe\" --version"
Open3.popen3(cmd) do |stdin, stdout, stderr, wait_thr|
  puts "stdout: #{stdout.read}"
  puts "\n\n"
  puts "stderr: #{stderr.read}"
end
Jay Godse
  • 15,163
  • 16
  • 84
  • 131
  • 1
    Don't use escaped escapes in a double-quoted string for a path. Instead do the simple thing and let Ruby figure out the path. Ruby will automatically correctly handle forward-slashes on Windows and convert them correctly. See the opening section of the [IO documentation](http://ruby-doc.org/core-2.3.1/IO.html) for more information. – the Tin Man Aug 18 '16 at 19:01
  • I used to work with a fellow nicknamed "The Tin Man" about 25 years ago at BNR. Is that you and are you John? – Jay Godse Aug 18 '16 at 19:10
  • Nope. That'd be someone else. – the Tin Man Aug 18 '16 at 19:22
0

Use Open3.popen3([bin, bin]) to prevent shell command handling for a single argument to popen3 (and related methods like spawn).

As you noticed, multiple args can be passed normally without invoking a shell (e.g. Open3.popen3(bin, arg1, arg2)).

ens
  • 1,068
  • 13
  • 14