Ruby: ¿Longitud de una línea de un archivo en bytes?
Pregunta
Estoy escribiendo este pequeño HelloWorld como seguimiento de this y los números no cuadran
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}"
El resultado no es el mismo que el tamaño del archivo. Creo que solo necesito saber qué formato
necesito conectar ... o tal vez me haya perdido el punto por completo. ¿Cómo puedo medir el tamaño del archivo línea por línea?
Nota: Estoy en Windows, y el archivo está codificado como tipo ANSI.
Editar: ¡Esto produce los mismos resultados!
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}"
así que cualquiera que pueda ayudar ahora ...
Solución
IO # gets funciona igual que si estuviera capturando datos desde la línea de comando: el " Enter " no se envía como parte de la entrada; tampoco se pasa cuando se invoca #gets en un archivo u otra subclase de IO, por lo que los números definitivamente no coincidirán.
Consulte la sección Pickaxe
relevante¿Puedo preguntar por qué está tan preocupado por las longitudes de línea que suman el tamaño del archivo? Es posible que esté resolviendo un problema más difícil de lo necesario ...
Ajá. Creo que lo entiendo ahora.
Al carecer de un iPod práctico (o de cualquier otro tipo, para el caso), no sé si quieres exactamente trozos 4K, en cuyo caso IO # read (4000) sería tu amigo (4000 o 4096?) o si está más contento de romper por línea, en cuyo caso algo como esto debería funcionar:
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
Tenga en cuenta el uso de IO # readlines para obtener todo el texto de una sola vez: #each o #each_line también funcionarían. ¡Utilicé String # chomp! para garantizar que, independientemente de lo que esté haciendo el sistema operativo, se eliminen los bytes al final, de modo que \ n o lo que sea se pueda forzar a la salida.
Sugeriría usar File # write, en lugar de #print o #puts para la salida, ya que estos últimos tienden a entregar secuencias de nueva línea específicas del sistema operativo.
Si realmente le preocupan los caracteres de varios bytes, considere tomar las opciones each_byte o unpack (C *) y la cadena de parches de mono, algo como esto:
<*>La versión descomprimida es aproximadamente 8 veces más rápida que la de cada byte en mi máquina, por cierto.
Otros consejos
Puede probar IO # each_byte, por ejemplo
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}"
Eso, por supuesto, no te da una línea a la vez. Su mejor opción para eso es probablemente revisar el archivo a través de each_byte
hasta que encuentre \ r \ n
. La clase IO proporciona un montón de métodos de lectura de nivel bastante bajo que podrían ser útiles.
Posiblemente tenga varios problemas superpuestos aquí:
-
Caracteres de salto de línea
\ r \ n
vs.\ n
(según su publicación anterior). ¿También el carácter de archivo EOF (^ Z)? -
Definición de " tamaño " en su enunciado del problema: ¿quiere decir "cuántos caracteres"? (teniendo en cuenta las codificaciones de caracteres de varios bytes) o quieres decir "cuántos bytes"?
-
Interacción de la variable global
$ KCODE
(en desuso en ruby ??1.9. VeaString # encoding
y amigos si está ejecutando bajo 1.9). ¿Hay, por ejemplo, caracteres acentuados en su archivo? -
Su cadena de formato para
#unpack
. Creo que quieresC *
aquí si realmente quieres contar bytes.
Tenga en cuenta también la existencia de IO # each_line
(solo para que pueda tirar el while
y ser un poco más rubí idiomático; -)).
El problema es que cuando guarda un archivo de texto en Windows, los saltos de línea son dos caracteres (caracteres 13 y 10) y, por lo tanto, 2 bytes, cuando lo guarda en Linux solo hay 1 (carácter 10). Sin embargo, ruby ??informa que ambos son un solo carácter '\ n': dice el carácter 10. Lo que es peor, es que si estás en Linux con un archivo de Windows, ruby ??te dará ambos caracteres.
Entonces, si sabe que sus archivos siempre provienen de archivos de texto de Windows y se ejecutan en Windows, cada vez que obtiene un carácter de nueva línea puede agregar 1 a su conteo. De lo contrario, son un par de condicionales y una pequeña máquina de estados.
Por cierto no hay EOF 'personaje'.
f = File.new("log.txt")
begin
while (line = f.readline)
line.chomp
puts line.length
end
rescue EOFError
f.close
end
Aquí hay una solución simple, suponiendo que el puntero del archivo actual esté configurado al comienzo de una línea en el archivo leído:
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)
en este ejemplo " archivo " es el archivo del que estás leyendo. Para hacer esto en un bucle:
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