Open3.popen3 returns 错误 Errno::ENOENT on Windows

Open3.popen3 returns wrong error Errno::ENOENT on Windows

我在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 是我创建的控制台应用程序,位于 C:\Program Files\foo\。当我 运行 bar.exe:

当我 运行 ruby test.rb 我得到这个错误:

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>'

如果我更改代码以调用 popen3:

Open3.popen3(cmd, '')

我没有收到 Errno::ENOENT 错误,而是收到帮助消息,但我想要 "Hello World" 输出。

我搜索了解决方案,但没有任何效果,包括“Why does Open3.popen3 return wrong error when executable is missing?”的答案。

为什么会出现此错误,我该如何解决?

您遇到问题是因为 "Program Files" 是一个包含 space 的文件夹。每当发生这种情况时,您都需要将其双引号,就像在 cmd.exe 提示符下一样。当你使用双引号时,你必须记住你的反斜杠字符“\”是一个转义字符,所以你必须使用双反斜杠以获得 Windows 的正确文件夹分隔符。我将使用在我的环境中实际上 returns 某些东西的代码;根据你的口味调整它。所以你的代码应该是这样的:

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

如果您有要传递给 git 的命令行参数,您可以这样做:

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

对此进行冥想:

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]

与第一个示例一样,您正在使用带有单反斜杠的双引号字符串作为 path/directory 分隔符。单个反斜杠 \f\b 是双引号字符串中的转义字符,无法识别,因为它们是使用 \ f\ b.

你有两种方法来处理这个问题,要么像第二个例子那样转义反斜杠,要么像第三个例子那样使用单引号字符串。使用第二种方式被认为是混乱的,所以使用最后一种方式以获得可读性和更容易维护。您将获得相同的字符,但视觉噪音更少。这适用于大多数语言中的字符串使用。

要知道的第二件事是 Ruby 不需要反斜杠作为路径分隔符。 IO documentation 表示:

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.

最后,您应该查看 STDLib 中的 Ruby 的 Shell and Shellwords。他们是你的朋友。

使用 Open3.popen3([bin, bin]) 来防止 shell 命令处理 popen3 的单个参数(以及 spawn 等相关方法)。

如您所见,可以正常传递多个参数而无需调用 shell(例如 Open3.popen3(bin, arg1, arg2)).