Open3.popen3 函数打开 bz、gz 和 txt 文件时出现“没有此类文件或目录”或“未打开读取”错误?

Den*_*din 2 ruby gzip ruby-on-rails bzip

我正在尝试编写一个实用程序函数来打开三种不同类型的文件:.bz2、.gz 和.txt。我不能只是使用File.read,因为它给我压缩文件带来了垃圾。我正在尝试使用,Open3.popen3以便可以给它一个不同的命令,但我收到以下代码的“没有这样的文件或目录”错误:

def file_info(file)
  cmd = ''
  if file.match("bz2") then
    cmd = "bzcat #{file}"# | head -20"
  elsif file.match("gz") then
    cmd = "gunzip -c #{file}"
  else
    cmd = "cat #{file}"
  end

  puts "opening file #{file}"
  Open3.popen3("#{cmd}", "r+") { |stdin, stdout, stderr|
    puts "stdin #{stdin.inspect}"
    stdin.read {|line|
      puts "line is #{line}"
      if line.match('^#') then
      else
        break
      end
    }
  }
end


> No such file or directory - cat /tmp/test.txt
Run Code Online (Sandbox Code Playgroud)

该文件确实存在。我尝试使用cmd而不是#{cmd}popen3 cmd.

我决定将其硬编码为 txt 文件,如下所示:

def file_info(file)
  puts "opening file #{file}"
  Open3.popen3("cat", file, "r+") { |stdin, stdout, stderr|
    puts "stdin #{stdin.inspect}"
    stdin.read {|line|
      puts "line is #{line}"
      if line.match('^#') then
      else
        break
      end
    }
  }
end
Run Code Online (Sandbox Code Playgroud)

这给了我回馈:

stdin #<IO:fd 6>
not opened for reading
Run Code Online (Sandbox Code Playgroud)

我究竟做错了什么?

当我做:

Open3.popen3("cat",file) { |stdin, stdout, stderr|
  puts "stdout is #{stdout.inspect}"
  stdout.read {|line|
    puts "line is #{line}"
    if line.match('^#') then
      puts "found line #{line}"
    else
      break
    end
  }
}
Run Code Online (Sandbox Code Playgroud)

我没有收到任何错误,并且打印了 STDOUT 行,但是这两个行语句都没有打印出任何内容。

在尝试了几种不同的方法之后,我想出的解决方案是:

cmd = Array.new
if file.match(/\.bz2\z/) then
  cmd = [ 'bzcat', file ]
elsif file.match(/\.gz\z/) then
  cmd = [ 'gunzip', '-c', file ]
else
  cmd = [ 'cat', file ]
end

Open3.popen3(*cmd) do |stdin, stdout, stderr|
  puts "stdout is #{stdout}"
  stdout.each do |line|
    if line.match('^#') then
      puts "line is #{line}"
    else
      break
    end
  end
end
Run Code Online (Sandbox Code Playgroud)

mu *_*ort 5

来自精美的手册(写得相当混乱):

* popen3( cmd, &block)
[...]
因此可以接受命令行字符串和参数字符串列表,如下所示。

Open3.popen3("echo a") {|i, o, e, t| ... }
Open3.popen3("echo", "a") {|i, o, e, t| ... }
Open3.popen3(["echo", "argv0"], "a") {|i, o, e, t| ... }
Run Code Online (Sandbox Code Playgroud)

所以当你这样做时:

Open3.popen3("cat /tmp/test.txt", "r+")
Run Code Online (Sandbox Code Playgroud)

popen3认为命令名称是cat /tmp/test.txt并且r+是该命令的参数,因此您看到的特定错误:

没有这样的文件或目录 - cat /tmp/test.txt

不需要通常的模式标志 ( "r+") Open3.popen3,因为它将分隔读取、写入和错误的句柄;而且,正如您所看到的,尝试提供模式字符串只会导致错误和混乱。

第二种情况:

Open3.popen3("cat", file, "r+") { |stdin, stdout, stderr|
  stdin.each {|line|
    #...
Run Code Online (Sandbox Code Playgroud)

不起作用,因为stdin这是命令的标准输入,而这就是您要写入的内容而不是从中读取的内容,而是您想要的stdout.read

您应该将命令构建为数组,并且您的match调用应该更严格一些:

if file.match(/\.bz2\z/) then
  cmd = [ 'bzcat', file ]
elsif file.match(/\.gz\z/) then
  cmd = [ 'gunzip', '-c', file ]
else
  cmd = [ 'cat', file ]
end
Run Code Online (Sandbox Code Playgroud)

然后打他们:

Open3.popen3(*cmd) do |stdin, stdout, stderr|
  #...
end
Run Code Online (Sandbox Code Playgroud)

这不仅有效,而且可以让您避免使用有趣的文件名。

您还可以通过跳过非压缩情况并使用它来避免无用的使用cat(有人可能会抱怨)。您可能还需要考虑检查文件的字节以查看它包含的内容,而不是依赖扩展名(或使用ruby​​-filemagic来检查)。Open3.popen3File.open