archivos - file ruby
Lea, edite y escriba un archivo de texto en lĂnea con Ruby (4)
En caso de que esté utilizando Rails o Facets , o si no depende de ActiveSupport Rails, puede usar la extensión atomic_write para File
:
File.atomic_write(''path/file'') do |file|
file.write(''your content'')
end
Detrás de escena, esto creará un archivo temporal que luego se moverá a la ruta deseada, teniendo cuidado de cerrar el archivo por usted.
Además clona los permisos de archivo del archivo existente o, si no hay uno, del directorio actual.
¿Hay una buena manera de leer, editar y escribir archivos en Ruby?
En mi búsqueda en línea he encontrado cosas que sugieren leer todo en una matriz, modificar dicha matriz, luego escribir todo. Siento que debería haber una solución mejor, especialmente si estoy tratando con un archivo muy grande.
Algo como:
myfile = File.open("path/to/file.txt", "r+")
myfile.each do |line|
myfile.replace_puts(''blah'') if line =~ /myregex/
end
myfile.close
Donde replace_puts
escribiría sobre la línea actual, en lugar de (sobre) escribir la siguiente línea como lo hace actualmente porque el puntero está al final de la línea (después del separador).
Entonces, cada línea que coincida con /myregex/
será reemplazada por ''blah''. Obviamente, lo que tengo en mente es un poco más complicado que eso, en cuanto al procesamiento, y lo haría en una línea, pero la idea es la misma: quiero leer un archivo línea por línea y editar ciertas líneas, y escribir cuando termine.
Tal vez hay una manera de decir "retroceder a justo después del último separador". ¿O alguna forma de usar each_with_index
y escribir a través de un número de índice de línea? Aunque no pude encontrar nada por el estilo.
La mejor solución que tengo hasta ahora es leer cosas en línea, escribirlas en un nuevo archivo (temporal) en línea (posiblemente editado), luego sobrescribir el archivo anterior con el nuevo archivo temporal y eliminarlo. De nuevo, creo que debería haber una mejor manera: no creo que deba crear un nuevo archivo 1gig solo para editar algunas líneas en un archivo de 1GB existente.
En general, no hay forma de realizar ediciones arbitrarias en el medio de un archivo. No es una deficiencia de Ruby. Es una limitación del sistema de archivos: la mayoría de los sistemas de archivos hacen que sea fácil y eficiente crecer o reducir el archivo al final, pero no al principio o en el medio. Por lo tanto, no podrá volver a escribir una línea en su lugar a menos que su tamaño se mantenga igual.
Hay dos modelos generales para modificar un grupo de líneas. Si el archivo no es demasiado grande, simplemente léalo todo a la memoria, modifíquelo y vuelva a escribirlo. Por ejemplo, agregar "Kilroy estuvo aquí" al comienzo de cada línea de un archivo:
path = ''/tmp/foo''
lines = IO.readlines(path).map do |line|
''Kilroy was here '' + line
end
File.open(path, ''w'') do |file|
file.puts lines
end
Aunque simple, esta técnica tiene un peligro: si el programa se interrumpe mientras escribe el archivo, perderá parte o la totalidad. También necesita usar memoria para contener todo el archivo. Si alguno de estos es un problema, entonces puede preferir la siguiente técnica.
Usted puede, como usted nota, escribir en un archivo temporal. Cuando termine, cambie el nombre del archivo temporal para que reemplace el archivo de entrada:
require ''tempfile''
require ''fileutils''
path = ''/tmp/foo''
temp_file = Tempfile.new(''foo'')
begin
File.open(path, ''r'') do |file|
file.each_line do |line|
temp_file.puts ''Kilroy was here '' + line
end
end
temp_file.close
FileUtils.mv(temp_file.path, path)
ensure
temp_file.close
temp_file.unlink
end
Como el cambio de nombre ( FileUtils.mv
) es atómico, el archivo de entrada reescrito aparecerá de FileUtils.mv
. Si el programa se interrumpe, el archivo se habrá reescrito o no lo hará. No hay posibilidad de que sea parcialmente reescrito.
La cláusula de garantía no es estrictamente necesaria: el archivo se eliminará cuando la instancia de Tempfile sea basura. Sin embargo, eso podría tomar un tiempo. El bloque ensure
asegura que el archivo temporal se limpia de inmediato, sin tener que esperar a que se recoja la basura.
Puede escribir en el medio de un archivo, pero debe tener cuidado de mantener la longitud de la cadena que sobrescribe la misma, de lo contrario sobrescribirá parte del texto siguiente. Doy un ejemplo aquí usando File.seek, IO :: SEEK_CUR le da la posición actual del puntero al archivo, al final de la línea que acaba de leer, el +1 es para el carácter CR al final de la línea.
look_for = "bbb"
replace_with = "xxxxx"
File.open(DATA, ''r+'') do |file|
file.each_line do |line|
if (line[look_for])
file.seek(-(line.length + 1), IO::SEEK_CUR)
file.write line.gsub(look_for, replace_with)
end
end
end
__END__
aaabbb
bbbcccddd
dddeee
eee
Después de ejecutarse, al final del script ahora tiene lo siguiente, no es lo que tenía en mente, supongo.
aaaxxxxx
bcccddd
dddeee
eee
Teniendo esto en cuenta, la velocidad con esta técnica es mucho mejor que el método clásico de "leer y escribir en un archivo nuevo". Vea estos puntos de referencia en un archivo con datos de música de 1.7 GB de tamaño. Para el enfoque clásico usé la técnica de Wayne. El punto de referencia se realiza con el método .bmbm, por lo que el almacenamiento en caché del archivo no representa un gran problema. Las pruebas se realizan con MRI Ruby 2.3.0 en Windows 7. Las cadenas se reemplazaron de manera efectiva, revisé ambos métodos.
require ''benchmark''
require ''tempfile''
require ''fileutils''
look_for = "Melissa Etheridge"
replace_with = "Malissa Etheridge"
very_big_file = ''D:/Documents/muziekinfo/all.txt''.gsub(''//',''/'')
def replace_with file_path, look_for, replace_with
File.open(file_path, ''r+'') do |file|
file.each_line do |line|
if (line[look_for])
file.seek(-(line.length + 1), IO::SEEK_CUR)
file.write line.gsub(look_for, replace_with)
end
end
end
end
def replace_with_classic path, look_for, replace_with
temp_file = Tempfile.new(''foo'')
File.foreach(path) do |line|
if (line[look_for])
temp_file.write line.gsub(look_for, replace_with)
else
temp_file.write line
end
end
temp_file.close
FileUtils.mv(temp_file.path, path)
ensure
temp_file.close
temp_file.unlink
end
Benchmark.bmbm do |x|
x.report("adapt ") { 1.times {replace_with very_big_file, look_for, replace_with}}
x.report("restore ") { 1.times {replace_with very_big_file, replace_with, look_for}}
x.report("classic adapt ") { 1.times {replace_with_classic very_big_file, look_for, replace_with}}
x.report("classic restore") { 1.times {replace_with_classic very_big_file, replace_with, look_for}}
end
Que dio
Rehearsal ---------------------------------------------------
adapt 6.989000 0.811000 7.800000 ( 7.800598)
restore 7.192000 0.562000 7.754000 ( 7.774481)
classic adapt 14.320000 9.438000 23.758000 ( 32.507433)
classic restore 14.259000 9.469000 23.728000 ( 34.128093)
----------------------------------------- total: 63.040000sec
user system total real
adapt 7.114000 0.718000 7.832000 ( 8.639864)
restore 6.942000 0.858000 7.800000 ( 8.117839)
classic adapt 14.430000 9.485000 23.915000 ( 32.195298)
classic restore 14.695000 9.360000 24.055000 ( 33.709054)
Entonces, el reemplazo in_file fue 4 veces más rápido.
Si desea sobrescribir un archivo línea por línea, deberá asegurarse de que la nueva línea tenga la misma longitud que la línea original. Si la nueva línea es más larga, parte de ella se escribirá en la siguiente línea. Si la nueva línea es más corta, el resto de la línea anterior simplemente permanece donde está. La solución de tempfile es realmente mucho más segura. Pero si estás dispuesto a correr un riesgo:
File.open(''test.txt'', ''r+'') do |f|
old_pos = 0
f.each do |line|
f.pos = old_pos # this is the ''rewind''
f.print line.gsub(''2010'', ''2011'')
old_pos = f.pos
end
end
Si el tamaño de la línea cambia, esta es una posibilidad:
File.open(''test.txt'', ''r+'') do |f|
out = ""
f.each do |line|
out << line.gsub(/myregex/, ''blah'')
end
f.pos = 0
f.print out
f.truncate(f.pos)
end