Функция Open3.popen3 для открытия ошибок в файлах bz, gz и txt с "Нет такого файла или каталога" или "не открыт для чтения"?

Я пытаюсь написать служебную функцию, которая будет открывать файлы трех разных типов: .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

Файл существует. Я пытался использовать cmd вместо #{cmd} с такими же результатами в popen3 cmd,

Я решил жестко закодировать его, чтобы сделать текстовый файл следующим образом:

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

Это возвращает меня

stdin #<IO:fd 6>
not opened for reading

Что я делаю неправильно?

Когда я делаю:

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

Я не получаю ошибок, и строка 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

2 ответа

Решение

Из прекрасного руководства (которое написано довольно смущающе):

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| ... }

Итак, когда вы делаете это:

Open3.popen3("cat /tmp/test.txt", "r+")

popen3 считает, что имя команды cat /tmp/test.txt а также r+ является аргументом этой команды, отсюда и конкретная ошибка, которую вы видите:

Нет такого файла или каталога - cat /tmp/test.txt

Там нет необходимости для обычных флагов режима ("r+") с Open3.popen3 поскольку он будет разделять дескрипторы для чтения, записи и ошибок; и, как вы видели, попытка указать строку режима просто вызывает ошибки и путаницу.

Второй случай:

Open3.popen3("cat", file, "r+") { |stdin, stdout, stderr|
  stdin.each {|line|
    #...

Не работает потому что 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

а затем разделить их

Open3.popen3(*cmd) do |stdin, stdout, stderr|
  #...
end

Это не только работает, но и избавит вас от забавных имен файлов.

Вы также можете избежать бесполезного использования cat (на что кто-то, вероятно, будет жаловаться), пропуская Open3.popen3 для несжатых случаев и использования File.open вместо. Вы также можете захотеть проверить байты файла, чтобы увидеть, что он содержит, а не полагаться на расширение (или использовать ruby-filemagic, чтобы проверить вас).

Вам лучше использовать bzip2-ruby и GzipReader для чтения соответствующих файлов. Открытие отдельного процесса для этого слишком дорого, сложно и хрупко.

Другие вопросы по тегам