Quais são as formas mais comuns de ler um arquivo no Ruby?

280

Quais são as formas mais comuns de ler um arquivo no Ruby?

Por exemplo, aqui está um método:

fileObj = File.new($fileName, "r")
while (line = fileObj.gets)
  puts(line)
end
fileObj.close

Eu sei que Ruby é extremamente flexível. Quais são os benefícios / desvantagens de cada abordagem?

dsg
fonte
6
Não acho que a resposta atual seja correta.
inger

Respostas:

259
File.open("my/file/path", "r") do |f|
  f.each_line do |line|
    puts line
  end
end
# File is closed automatically at end of block

Também é possível fechar explicitamente o arquivo depois como acima (passe um bloco para fechá- openlo para você):

f = File.open("my/file/path", "r")
f.each_line do |line|
  puts line
end
f.close
fl00r
fonte
14
Isso não é Ruby idiomático. Use em foreachvez de opene dispense o each_linebloco.
the Tin Man
7
f.each { |line| ... }e f.each_line { |line| ... }parece ter o mesmo comportamento (pelo menos no Ruby 2.0.0).
chbrown
327

A maneira mais fácil se o arquivo não for muito longo é:

puts File.read(file_name)

De fato, IO.readou File.readfeche o arquivo automaticamente, para que não haja necessidade de usar File.opencom um bloco.

mckeed
fonte
16
IO.readou File.readfeche o arquivo automaticamente, embora suas palavras pareçam não parecer.
Phrtz #
15
ele já disse "se o arquivo não for muito longo". Serve perfeitamente ao meu caso.
jayP
227

Desconfie de arquivos "slurping". É quando você lê o arquivo inteiro na memória de uma só vez.

O problema é que ele não escala bem. Você pode desenvolver código com um arquivo de tamanho razoável, colocá-lo em produção e de repente descobrir que está tentando ler arquivos medidos em gigabytes, e seu host está congelando ao tentar ler e alocar memória.

A E / S linha a linha é muito rápida e quase sempre tão eficaz quanto o slurping. É surpreendentemente rápido, na verdade.

Eu gosto de usar:

IO.foreach("testfile") {|x| print "GOT ", x }

ou

File.foreach('testfile') {|x| print "GOT", x }

O arquivo é herdado do IO e foreachestá no IO, para que você possa usá-lo.

Eu tenho alguns benchmarks mostrando o impacto de tentar ler arquivos grandes por meio de readE / S linha a linha em " Por que" compactar "um arquivo não é uma boa prática? ".

do homem de lata
fonte
6
Era exatamente isso que eu estava procurando. Eu tenho um arquivo com cinco milhões de linhas e realmente não queria que isso fosse carregado na memória.
Scotty C.
68

Você pode ler o arquivo de uma só vez:

content = File.readlines 'file.txt'
content.each_with_index{|line, i| puts "#{i+1}: #{line}"}

Quando o arquivo é grande, ou pode ser grande, geralmente é melhor processá-lo linha por linha:

File.foreach( 'file.txt' ) do |line|
  puts line
end

Às vezes, você deseja acessar o identificador de arquivo ou controlar as leituras:

File.open( 'file.txt' ) do |f|
  loop do
    break if not line = f.gets
    puts "#{f.lineno}: #{line}"
  end
end

No caso de arquivos binários, você pode especificar um separador nulo e um tamanho de bloco, assim:

File.open('file.bin', 'rb') do |f|
  loop do
    break if not buf = f.gets(nil, 80)
    puts buf.unpack('H*')
  end
end

Finalmente, você pode fazer isso sem um bloco, por exemplo, ao processar vários arquivos simultaneamente. Nesse caso, o arquivo deve ser explicitamente fechado (aprimorado conforme o comentário de @antinome):

begin
  f = File.open 'file.txt'
  while line = f.gets
    puts line
  end
ensure
  f.close
end

Referências: API do arquivo e a API do IO .

Victor Klos
fonte
2
Não há for_eachno arquivo ou no IO. Use em foreachvez disso.
the Tin Man
1
Normalmente, uso o editor de texto sublime, com o plug-in RubyMarkers, ao documentar o código a ser usado nas respostas aqui. Torna realmente fácil mostrar resultados intermediários, semelhante ao uso do IRB. Além disso, o plug-in Seeing Is Believing para Sublime Text 2 é realmente poderoso.
the Tin Man
1
Ótima resposta. Para o último exemplo, sugiro usar em whilevez de loope usando ensurepara garantir que o arquivo seja fechado, mesmo que uma exceção seja gerada. Como este (substitua ponto e vírgula com novas linhas): begin; f = File.open('testfile'); while line = f.gets; puts line; end; ensure; f.close; end.
Antinome
1
sim, isso é muito melhor @antinome, melhorou a resposta. obrigado!
Victor Klos
26

Um método simples é usar readlines:

my_array = IO.readlines('filename.txt')

Cada linha no arquivo de entrada será uma entrada na matriz. O método controla a abertura e o fechamento do arquivo para você.

bta
fonte
5
Assim como em readqualquer outra variante, isso puxará o arquivo inteiro para a memória, o que pode causar grandes problemas se o arquivo for maior que a memória disponível. Além disso, por ser uma matriz, Ruby precisa criar a matriz, retardando o processo adicionalmente.
o homem de lata
9

Eu costumo fazer isso:

open(path_in_string, &:read)

Isso fornecerá o texto inteiro como um objeto de string. Funciona apenas no Ruby 1.9.

serra
fonte
Isso é legal e curto! Também fecha o arquivo?
mrgreenfur
5
Ele fecha, mas não é escalável, portanto, tenha cuidado.
the Tin Man
3

retorna as últimas n linhas de your_file.log ou .txt

path = File.join(Rails.root, 'your_folder','your_file.log')

last_100_lines = `tail -n 100 #{path}`
Alex Danko
fonte
1

Uma maneira ainda mais eficiente é fazer o streaming solicitando ao kernel do sistema operacional que abra um arquivo e, em seguida, leia os bytes bit a bit. Ao ler um arquivo por linha no Ruby, os dados são retirados do arquivo 512 bytes por vez e divididos em "linhas" depois disso.

Ao armazenar em buffer o conteúdo do arquivo, o número de chamadas de E / S é reduzido ao dividir o arquivo em blocos lógicos.

Exemplo:

Adicione esta classe ao seu aplicativo como um objeto de serviço:

class MyIO
  def initialize(filename)
    fd = IO.sysopen(filename)
    @io = IO.new(fd)
    @buffer = ""
  end

  def each(&block)
    @buffer << @io.sysread(512) until @buffer.include?($/)

    line, @buffer = @buffer.split($/, 2)

    block.call(line)
    each(&block)
  rescue EOFError
    @io.close
 end
end

Chame-o e passe ao :eachmétodo um bloco:

filename = './somewhere/large-file-4gb.txt'
MyIO.new(filename).each{|x| puts x }

Leia sobre isso aqui neste post detalhado:

Arquivos Ruby Magic Slurping & Streaming por AppSignal

Khalil Gharbaoui
fonte
Cuidado: esse código ignorará a última linha se não terminar com um avanço de linha (pelo menos no Linux).
Jorgen
Acho que a inserção de "block.call (@buffer)" antes de "@ io.close" pegará a linha incompleta ausente. No entanto, joguei com Ruby apenas um dia, para poder estar errado. Ele trabalhou no meu aplicativo :)
Jorgen
Depois de ler o post do AppSignal, parece que houve um pequeno mal-entendido aqui. O código que você copiou dessa postagem que executa um IO em buffer é um exemplo de implementação do que o Ruby realmente faz com File.foreach ou IO.foreach (que são o mesmo método). Eles devem ser usados ​​e você não precisa reimplementá-los dessa maneira.
Peter H. Boling
@ PeterH.Boling Também sou a favor da mentalidade de usar e não reimplementar a maior parte do tempo. Mas o rubi nos permite abrir as coisas e cutucar o interior delas sem vergonha, é uma de suas vantagens. Não existe um verdadeiro 'deveria' ou 'não deveria', especialmente em rubis / trilhos. Contanto que você saiba o que está fazendo e escreva testes para isso.
Khalil Gharbaoui
0
content = `cat file`

Eu acho que esse método é o mais "incomum". Talvez seja meio complicado, mas funciona se catestiver instalado.

helloqiu
fonte
1
Um truque útil, mas chamar o shell tem muitas armadilhas, incluindo 1) os comandos podem diferir em diferentes sistemas operacionais, 2) você pode precisar escapar de espaços no nome do arquivo. Você está muito melhor usando Ruby funções embutidas, por exemplocontent = File.read(filename)
Jeff Ward