Quelles sont toutes les façons courantes de lire un fichier dans Ruby?

280

Quelles sont toutes les façons courantes de lire un fichier dans Ruby?

Par exemple, voici une méthode:

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

Je sais que Ruby est extrêmement flexible. Quels sont les avantages / inconvénients de chaque approche?

dsg
la source
6
Je ne pense pas que la réponse gagnante actuelle soit correcte.
inger

Réponses:

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

Il est également possible de fermer explicitement le fichier après comme ci-dessus (passez un bloc pour le openfermer pour vous):

f = File.open("my/file/path", "r")
f.each_line do |line|
  puts line
end
f.close
fl00r
la source
14
Ce Ruby n'est guère idiomatique. Utilisez foreachplutôt openle each_linebloc et supprimez-le .
The Tin Man
7
f.each { |line| ... }et f.each_line { |line| ... }semblent avoir le même comportement (au moins dans Ruby 2.0.0).
chbrown
327

Le moyen le plus simple si le fichier n'est pas trop long est:

puts File.read(file_name)

En effet, IO.readou File.readfermez automatiquement le fichier, il n'est donc pas nécessaire de l'utiliser File.openavec un bloc.

mckeed
la source
16
IO.readou File.readfermez également automatiquement le fichier, bien que votre libellé donne l'impression que ce n'est pas le cas.
Phrogz
15
il a déjà dit "si le dossier n'est pas trop long". Convient parfaitement à mon cas.
jayP
227

Méfiez-vous des fichiers "slurping". C'est alors que vous lisez le fichier entier en mémoire à la fois.

Le problème est qu'il ne se modifie pas bien. Vous pourriez développer du code avec un fichier de taille raisonnable, puis le mettre en production et découvrir soudain que vous essayez de lire des fichiers mesurant en gigaoctets, et votre hôte se bloque alors qu'il essaie de lire et d'allouer de la mémoire.

Les E / S ligne par ligne sont très rapides et presque toujours aussi efficaces que le slurping. C'est étonnamment rapide en fait.

J'aime utiliser:

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

ou

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

Le fichier hérite d'IO et se foreachtrouve dans IO, vous pouvez donc utiliser l'un ou l'autre.

J'ai quelques points de repère montrant l'impact d'essayer de lire de gros fichiers via des readE / S ligne par ligne sur " Pourquoi" slurping "un fichier n'est-il pas une bonne pratique? ".

le Tin Man
la source
6
Ceci est exactement ce que je cherchais. J'ai un fichier de cinq millions de lignes et je ne voulais vraiment pas qu'il soit chargé en mémoire.
Scotty C.
68

Vous pouvez lire le fichier en une seule fois:

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

Lorsque le fichier est volumineux ou peut être volumineux, il est généralement préférable de le traiter ligne par ligne:

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

Parfois, vous souhaitez accéder au descripteur de fichier ou contrôler vous-même les lectures:

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

Dans le cas de fichiers binaires, vous pouvez spécifier un séparateur nul et une taille de bloc, comme ceci:

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

Enfin, vous pouvez le faire sans bloc, par exemple lors du traitement de plusieurs fichiers simultanément. Dans ce cas, le fichier doit être explicitement fermé (amélioré selon le commentaire de @antinome):

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

Références: File API et IO API .

Victor Klos
la source
2
Il n'y for_eachen a pas dans File ou IO. Utilisez foreachplutôt.
The Tin Man
1
J'utilise généralement l'éditeur Sublime Text, avec le plugin RubyMarkers, pour documenter le code à utiliser dans les réponses ici. Il est très facile d'afficher des résultats intermédiaires, similaires à l'utilisation de l'IRB. Le plugin Seeing Is Believing pour Sublime Text 2 est également très puissant.
The Tin Man
1
Très bonne réponse. Pour le dernier exemple, je pourrais suggérer d'utiliser whileau lieu de loopet d'utiliser ensurepour garantir la fermeture du fichier même si une exception est levée. Comme cela (remplacer les points-virgules avec des sauts de ligne): begin; f = File.open('testfile'); while line = f.gets; puts line; end; ensure; f.close; end.
antinome
1
ouais c'est beaucoup mieux @antinome, a amélioré la réponse. Merci!
Victor Klos
26

Une méthode simple consiste à utiliser readlines:

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

Chaque ligne du fichier d'entrée sera une entrée dans le tableau. La méthode gère l'ouverture et la fermeture du fichier pour vous.

bta
la source
5
Comme avec readou toute variante, cela va tirer tout le fichier en mémoire, ce qui peut provoquer des problèmes majeurs si le fichier est plus grand que la mémoire disponible. De plus, comme il s'agit d'un tableau, Ruby doit créer le tableau, ce qui ralentit également le processus.
The Tin Man
9

Je fais habituellement ceci:

open(path_in_string, &:read)

Cela vous donnera le texte entier sous forme d'objet chaîne. Cela ne fonctionne que sous Ruby 1.9.

sawa
la source
C'est gentil et bref! Ferme-t-il également le fichier?
mrgreenfur
5
Il le ferme, mais il n'est pas évolutif, alors soyez prudent.
The Tin Man
3

retourne les n dernières lignes 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
la source
1

Une manière encore plus efficace est la diffusion en continu en demandant au noyau du système d'exploitation d'ouvrir un fichier, puis de lire les octets peu à peu. Lors de la lecture d'un fichier par ligne dans Ruby, les données sont extraites du fichier 512 octets à la fois et divisées en «lignes» après cela.

En mettant en mémoire tampon le contenu du fichier, le nombre d'appels d'E / S est réduit tout en divisant le fichier en segments logiques.

Exemple:

Ajoutez cette classe à votre application en tant qu'objet de service:

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

Appelez-le et passez la :eachméthode un bloc:

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

Lisez à ce sujet ici dans cet article détaillé:

Ruby Magic Slurping & Streaming Files par AppSignal

Khalil Gharbaoui
la source
Attention: ce code ignorera la dernière ligne s'il ne se termine pas par un saut de ligne (au moins sous Linux).
Jorgen
Je pense que l'insertion de "block.call (@buffer)" avant "@ io.close" ramènera la ligne incomplète manquante. Cependant, je n'ai joué avec Ruby qu'une seule journée donc je peux très bien me tromper. Cela a fonctionné dans mon application :)
Jorgen
Après avoir lu le post AppSignal, il semble qu'il y ait eu un petit malentendu ici. Le code que vous avez copié à partir de cette publication et qui fait un IO tamponné est un exemple d'implémentation de ce que Ruby fait réellement avec File.foreach ou IO.foreach (qui sont la même méthode). Ils doivent être utilisés et vous n'avez pas besoin de les réimplémenter comme ça.
Peter H. Boling
@ PeterH.Boling Je suis aussi pour la mentalité d'utilisation et de non-réimplémentation la plupart du temps. Mais le rubis nous permet d'ouvrir les choses et de les fouiller sans honte, c'est l'un de ses avantages. Il n'y a pas vraiment de «devrait» ou «ne devrait pas» en particulier dans les rubis / rails. Tant que vous savez ce que vous faites et que vous écrivez des tests pour cela.
Khalil Gharbaoui
0
content = `cat file`

Je pense que cette méthode est la plus "rare". C'est peut-être un peu délicat, mais cela fonctionne si catest installé.

helloqiu
la source
1
Une astuce pratique, mais appeler le shell présente de nombreux pièges, y compris 1) les commandes peuvent différer sur différents systèmes d'exploitation, 2) vous devrez peut-être échapper des espaces dans le nom de fichier. Il vaut mieux utiliser les fonctions intégrées de Ruby, par exemplecontent = File.read(filename)
Jeff Ward