Функция 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 для чтения соответствующих файлов. Открытие отдельного процесса для этого слишком дорого, сложно и хрупко.