Ruby: Longueur d'une ligne d'un fichier en octets?
Question
J'écris ce petit HelloWorld à la suite de ceci et les chiffres ne concordent pas
filename = "testThis.txt"
total_bytes = 0
file = File.new(filename, "r")
file.each do |line|
total_bytes += line.unpack("U*").length
end
puts "original size #{File.size(filename)}"
puts "Total bytes #{total_bytes}"
Le résultat n'est pas identique à la taille du fichier. Je pense que je dois juste savoir quel format
je dois me brancher ... ou peut-être ai-je tout manqué. Comment puis-je mesurer la taille du fichier ligne par ligne?
Remarque: je suis sous Windows et le fichier est codé en tant que type ANSI.
Modifier: Cela produit les mêmes résultats!
filename = "testThis.txt"
total_bytes = 0
file = File.new(filename, "r")
file.each_byte do |whatever|
total_bytes += 1
end
puts "Original size #{File.size(filename)}"
puts "Total bytes #{total_bytes}"
donc quiconque peut aider maintenant ...
La solution
IO # gets fonctionne de la même manière que si vous capturiez une entrée à partir de la ligne de commande: le " Entrée " n'est pas envoyé dans le cadre de l'entrée; elle n'est pas non plus transmise lorsque #gets est appelé dans un fichier ou dans une autre sous-classe d'IO, de sorte que les nombres ne vont certainement pas correspondre.
Voir la section de la pioche correspondante
Puis-je savoir pourquoi vous êtes si préoccupé par la longueur des lignes qui correspond à la taille du fichier? Vous résolvez peut-être un problème plus difficile que nécessaire ...
Aha. Je pense que je l’ai maintenant.
En l'absence d'un iPod pratique (ou de tout autre type, d'ailleurs), je ne sais pas si vous voulez exactement des morceaux de 4K, auquel cas IO # read (4000) serait votre ami (4000 ou 4096?) ou si vous êtes plus en mesure de faire la ligne par ligne, dans ce cas, une solution de ce type devrait fonctionner:
class Chunkifier
def Chunkifier.to_chunks(path)
chunks, current_chunk_size = [""], 0
File.readlines(path).each do |line|
line.chomp! # strips off \n, \r or \r\n depending on OS
if chunks.last.size + line.size >= 4_000 # 4096?
chunks.last.chomp! # remove last line terminator
chunks << ""
end
chunks.last << line + "\n" # or whatever terminator you need
end
chunks
end
end
if __FILE__ == class String
def size_in_bytes
self.unpack("C*").size
end
end
require 'test/unit'
class TestFile < Test::Unit::TestCase
def test_chunking
chs = Chunkifier.to_chunks(PATH)
chs.each do |chunk|
assert 4_000 >= chunk.size, "chunk is #{chunk.size} bytes long"
end
end
end
end
Notez que l'utilisation des lignes de texte IO # pour obtenir tout le texte dans un slurp: #each ou #each_line conviendrait également. J'ai utilisé String # chomp! pour vous assurer que quoi que fasse le système d'exploitation, les octets à la fin soient supprimés, de sorte que \ n ou quoi que ce soit puisse être forcé dans la sortie.
Je suggérerais d'utiliser Fichier # write plutôt que #print ou #puts pour la sortie, car ces derniers ont tendance à fournir des séquences de nouvelle ligne spécifiques à un système d'exploitation.
Si vous êtes vraiment préoccupé par les caractères multi-octets, envisagez d'utiliser les options each_byte ou unpack (C *) et la chaîne monkey-patcher, comme ceci:
<*>La version de décompression est environ 8 fois plus rapide que celle de each_byte sur ma machine, d'ailleurs.
Autres conseils
Vous pouvez essayer IO # each_byte, par exemple.
total_bytes = 0
file_name = "test_this.txt"
File.open(file_name, "r") do |file|
file.each_byte {|b| total_bytes += 1}
end
puts "Original size #{File.size(file_name)}"
puts "Total bytes #{total_bytes}"
Cela, bien sûr, ne vous donne pas une ligne à la fois. Votre meilleure option pour cela consiste probablement à parcourir le fichier via chaque_byte
jusqu'à ce que vous rencontriez \ r \ n
. La classe IO fournit un ensemble de méthodes de lecture assez simples qui pourraient être utiles.
Vous avez potentiellement plusieurs problèmes qui se chevauchent ici:
-
Caractères de saut de ligne
\ r \ n
vs.\ n
(comme dans votre message précédent). Aussi le caractère de fichier EOF (^ Z)? -
Définition de " taille " dans votre énoncé de problème: voulez-vous dire "combien de caractères"? (en tenant compte des codages de caractères multi-octets) ou voulez-vous dire "combien d'octets"?
-
Interaction de la variable globale
$ KCODE
(obsolète dans Ruby 1.9. VoirString # encoding
et amis si vous utilisez une version inférieure à 1.9). Existe-t-il, par exemple, des caractères accentués dans votre fichier? -
Votre chaîne de format pour
#unpack
. Je pense que vous voulezC *
ici si vous voulez vraiment compter les octets.
Notez également l'existence de IO # each_line
(afin que vous puissiez jeter le tant que
et être un peu plus ruby-idiomatique; -)).
Le problème est que lorsque vous enregistrez un fichier texte sous Windows, vos sauts de ligne sont composés de deux caractères (caractères 13 et 10) et donc de 2 octets. Lorsque vous l’enregistrez sur linux, il n’ya qu’un (caractère 10). Cependant, ruby ??les signale comme un seul caractère '\ n' - il indique le caractère 10. Le pire est que si vous êtes sur Linux avec un fichier Windows, Ruby vous donnera les deux caractères.
Ainsi, si vous savez que vos fichiers proviennent toujours de fichiers texte Windows et sont exécutés sous Windows, vous pouvez ajouter 1 à votre compte à chaque nouvelle ligne. Sinon, il s’agit de deux conditions et d’une petite machine à états.
BTW il n'y a pas de 'caractère' EOF.
f = File.new("log.txt")
begin
while (line = f.readline)
line.chomp
puts line.length
end
rescue EOFError
f.close
end
Voici une solution simple, en supposant que le pointeur de fichier actuel est défini au début d'une ligne dans le fichier en lecture:
last_pos = file.pos
next_line = file.gets
current_pos = file.pos
backup_dist = last_pos - current_pos
file.seek(backup_dist, IO::SEEK_CUR)
dans cet exemple, "fichier". est le fichier à partir duquel vous lisez. Pour le faire en boucle:
last_pos = file.pos
begin loop
next_line = file.gets
current_pos = file.pos
backup_dist = last_pos - current_pos
last_pos = current_pos
file.seek(backup_dist, IO::SEEK_CUR)
end loop