En las respuestas a continuación, tenga en cuenta que cualquier recomendación de uso File.readdebe moderarse con la información en stackoverflow.com/a/25189286/128421 sobre por qué es malo sorber archivos grandes. Además, en lugar de File.open(filename, "w") { |file| file << content }utilizar variaciones File.write(filename, content).
The Tin Man
Respuestas:
190
Descargo de responsabilidad: este enfoque es una ilustración ingenua de las capacidades de Ruby y no una solución de producción para reemplazar cadenas en archivos. Es propenso a varios escenarios de falla, como la pérdida de datos en caso de una falla, una interrupción o un disco lleno. Este código no es apto para nada más que un script rápido y único en el que se realiza una copia de seguridad de todos los datos. Por esa razón, NO copie este código en sus programas.
Aquí hay una forma rápida y corta de hacerlo.
file_names =['foo.txt','bar.txt']
file_names.each do|file_name|
text =File.read(file_name)
new_contents = text.gsub(/search_regexp/,"replacement string")# To merely print the contents of the file, use:
puts new_contents
# To write changes to the file, use:File.open(file_name,"w"){|file| file.puts new_contents }end
Para escribir un archivo, reemplace la línea File.write(file_name, text.gsub(/regexp/, "replace")
apretado
106
De hecho, Ruby tiene una función de edición in situ. Como Perl, puedes decir
ruby -pi.bak -e "gsub(/oldtext/, 'newtext')"*.txt
Esto aplicará el código entre comillas dobles a todos los archivos del directorio actual cuyos nombres terminen con ".txt". Las copias de seguridad de los archivos editados se crearán con una extensión ".bak" (creo que "foobar.txt.bak").
NOTA: esto no parece funcionar para búsquedas de varias líneas. Para esos, debe hacerlo de la otra manera menos bonita, con un script de envoltura alrededor de la expresión regular.
¿Qué diablos es pi.bak? Sin eso, obtengo un error. -e: 1: en <main>': undefined method gsub 'para main: Object (NoMethodError)
Ninad
15
@NinadPachpute -iediciones en su lugar. .bakes la extensión utilizada para un archivo de respaldo (opcional). -pes algo así como while gets; <script>; puts $_; end. ( $_es la última línea leída, pero puede asignarle algo como echo aa | ruby -p -e '$_.upcase!').
Lri
1
Esta es una mejor respuesta que la respuesta aceptada, en mi humilde opinión, si está buscando modificar el archivo.
Colin K
6
¿Cómo puedo usar esto dentro de un script ruby?
Saurabh
1
Hay muchas formas en que esto puede salir mal, así que pruébelo a fondo antes de intentarlo con un archivo crítico.
The Tin Man
49
Tenga en cuenta que, cuando haga esto, el sistema de archivos podría quedarse sin espacio y puede crear un archivo de longitud cero. Esto es catastrófico si está haciendo algo como escribir archivos / etc / passwd como parte de la administración de la configuración del sistema.
Tenga en cuenta que la edición de archivos en el lugar como en la respuesta aceptada siempre truncará el archivo y escribirá el nuevo archivo secuencialmente. Siempre habrá una condición de carrera en la que los lectores simultáneos verán un archivo truncado. Si el proceso se aborta por cualquier motivo (ctrl-c, asesino de OOM, caída del sistema, corte de energía, etc.) durante la escritura, el archivo truncado también quedará, lo que puede ser catastrófico. Este es el tipo de escenario de pérdida de datos que los desarrolladores DEBEN considerar porque sucederá. Por esa razón, creo que la respuesta aceptada probablemente no debería ser la respuesta aceptada. Como mínimo, escriba en un archivo temporal y mueva / cambie el nombre del archivo en su lugar como la solución "simple" al final de esta respuesta.
Necesita utilizar un algoritmo que:
Lee el archivo antiguo y escribe en el nuevo archivo. (Debe tener cuidado al absorber archivos completos en la memoria).
Cierra explícitamente el nuevo archivo temporal, que es donde puede lanzar una excepción porque los búferes de archivos no se pueden escribir en el disco porque no hay espacio. (Capture esto y limpie el archivo temporal si lo desea, pero necesita volver a lanzar algo o fallar bastante en este punto.
Corrige los permisos y modos de archivo en el nuevo archivo.
Cambia el nombre del nuevo archivo y lo coloca en su lugar.
Con los sistemas de archivos ext3, tiene la garantía de que la escritura de metadatos para mover el archivo a su lugar no será reorganizada por el sistema de archivos y escrita antes de que se escriban los búferes de datos para el nuevo archivo, por lo que esto debería tener éxito o fallar. El sistema de archivos ext4 también ha sido parcheado para admitir este tipo de comportamiento. Si está muy paranoico, debe llamar a la llamada al fdatasync()sistema como paso 3.5 antes de mover el archivo a su lugar.
Independientemente del idioma, esta es la mejor práctica. En los lenguajes donde la llamada close()no arroja una excepción (Perl o C), debe verificar explícitamente el retorno de close()y lanzar una excepción si falla.
La sugerencia anterior de simplemente absorber el archivo en la memoria, manipularlo y escribirlo en el archivo garantizará la producción de archivos de longitud cero en un sistema de archivos completo. Es necesario que siempre se utilice FileUtils.mvpara mover un archivo temporal totalmente escrito en su lugar.
Una consideración final es la ubicación del archivo temporal. Si abre un archivo en / tmp, debe considerar algunos problemas:
Si / tmp está montado en un sistema de archivos diferente, puede ejecutar / tmp sin espacio antes de haber escrito el archivo que, de lo contrario, se podría implementar en el destino del archivo anterior.
Probablemente, lo que es más importante, cuando intente mvel archivo a través de un montaje de dispositivo, se convertirá de manera transparente en cpcomportamiento. El archivo antiguo se abrirá, el inodo de archivos antiguos se conservará y se volverá a abrir y se copiará el contenido del archivo. Lo más probable es que esto no sea lo que desea y puede encontrarse con errores de "archivo de texto ocupado" si intenta editar el contenido de un archivo en ejecución. Esto también anula el propósito de usar los mvcomandos del sistema de archivos y puede ejecutar el sistema de archivos de destino sin espacio con solo un archivo escrito parcialmente.
Esto tampoco tiene nada que ver con la implementación de Ruby. El sistema mvy los cpcomandos se comportan de manera similar.
Lo que es más preferible es abrir un Tempfile en el mismo directorio que el archivo anterior. Esto asegura que no habrá problemas de movimiento entre dispositivos. El mvmismo nunca debería fallar, y siempre debería obtener un archivo completo y sin truncar. Cualquier falla, como dispositivo sin espacio, errores de permisos, etc., debe encontrarse durante la escritura del Tempfile.
Las únicas desventajas del enfoque de crear el archivo temporal en el directorio de destino son:
A veces, es posible que no pueda abrir un archivo temporal allí, por ejemplo, si está intentando 'editar' un archivo en / proc. Por esa razón, es posible que desee retroceder y probar / tmp si falla la apertura del archivo en el directorio de destino.
Debe tener suficiente espacio en la partición de destino para contener tanto el archivo antiguo completo como el nuevo. Sin embargo, si no tiene suficiente espacio para guardar ambas copias, entonces probablemente tenga poco espacio en el disco y el riesgo real de escribir un archivo truncado es mucho mayor, por lo que yo diría que esta es una compensación muy pobre fuera de algunas extremadamente estrechas (y bueno -monitorizados) casos extremos.
Aquí hay un código que implementa el algoritmo completo (el código de Windows no está probado y sin terminar):
El caso de uso realmente simple, para cuando no le importan los permisos del sistema de archivos (o no se está ejecutando como root o se está ejecutando como root y el archivo es propiedad de root):
TL; DR : Debe usarse en lugar de la respuesta aceptada como mínimo, en todos los casos, para garantizar que la actualización sea atómica y que los lectores simultáneos no vean archivos truncados. Como mencioné anteriormente, crear el Tempfile en el mismo directorio que el archivo editado es importante aquí para evitar que las operaciones mv entre dispositivos se traduzcan en operaciones cp si / tmp está montado en un dispositivo diferente. Llamar a fdatasync es una capa adicional de paranoia, pero generará un impacto en el rendimiento, por lo que lo omití de este ejemplo ya que no se practica comúnmente.
En lugar de abrir un archivo temporal en el directorio en el que se encuentra, se creará automáticamente uno en el directorio de datos de la aplicación (de todos modos en Windows) y desde allí puede hacer un archivo.unlink para eliminarlo ..
13aal
3
Realmente aprecié el pensamiento adicional que se puso en esto. Como principiante, es muy interesante ver los patrones de pensamiento de desarrolladores experimentados que no solo pueden responder la pregunta original, sino también comentar sobre el contexto más amplio de lo que realmente significa la pregunta original.
ramijames
La programación no se trata solo de solucionar el problema inmediato, también se trata de pensar con anticipación para evitar otros problemas al acecho. Nada irrita más a un desarrollador senior que encontrar un código que colocó el algoritmo en una esquina, forzando un torpeza incómodo, cuando un pequeño ajuste anterior hubiera resultado en un buen flujo. A menudo, puede llevar horas o días analizar para comprender el objetivo, y luego unas pocas líneas reemplazan una página de código antiguo. Es como una partida de ajedrez contra los datos y el sistema a veces.
The Tin Man
11
Realmente no hay una forma de editar archivos in situ. Lo que suele hacer cuando puede salirse con la suya (es decir, si los archivos no son demasiado grandes) es leer el archivo en la memoria ( File.read), realizar sus sustituciones en la cadena de lectura ( String#gsub) y luego escribir la cadena cambiada de nuevo en la archivo ( File.open, File#write).
Si los archivos son lo suficientemente grandes como para que eso sea inviable, lo que debe hacer es leer el archivo en fragmentos (si el patrón que desea reemplazar no abarca varias líneas, entonces un fragmento generalmente significa una línea; puede usar File.foreachpara leer un archivo línea por línea), y para cada fragmento, realice la sustitución y añádalo a un archivo temporal. Cuando haya terminado de iterar sobre el archivo fuente, ciérrelo y utilice FileUtils.mvpara sobrescribirlo con el archivo temporal.
Me gusta el enfoque de transmisión. Trabajamos con archivos grandes al mismo tiempo, por lo que generalmente no tenemos el espacio en la RAM para leer el archivo completo
Otro enfoque es usar la edición in situ dentro de Ruby (no desde la línea de comando):
#!/usr/bin/rubydef inplace_edit(file, bak,&block)
old_stdout = $stdout
argf = ARGF.clone
argf.argv.replace [file]
argf.inplace_mode = bak
argf.each_line do|line|yield line
end
argf.close
$stdout = old_stdout
end
inplace_edit 'test.txt','.bak'do|line|
line = line.gsub(/search1/,"replace1")
line = line.gsub(/search2/,"replace2")
print line unless line.match(/something/)end
Si no desea crear una copia de seguridad, cambie '.bak'a ''.
Esto sería mejor que intentar sorber ( read) el archivo. Es escalable y debería ser muy rápido.
The Tin Man
Hay un error en alguna parte que hace que Ruby 2.3.0p0 en Windows falle con el permiso denegado si hay varios bloques inplace_edit consecutivos trabajando en el mismo archivo. Para reproducir pruebas divididas search1 y search2 en 2 bloques. ¿No se cierra por completo?
mlt
Esperaría que se produjeran problemas con varias ediciones de un archivo de texto simultáneamente. Si nada más, podría obtener un archivo de texto muy destrozado.
Aquí hay una solución para buscar / reemplazar en todos los archivos de un directorio determinado. Básicamente tomé la respuesta proporcionada por sepp2k y la expandí.
# First set the files to search/replace in
files =Dir.glob("/PATH/*")# Then set the variables for find/replace@original_string_or_regex=/REGEX/@replacement_string="STRING"
files.each do|file_name|
text =File.read(file_name)
replace = text.gsub!(@original_string_or_regex,@replacement_string)File.open(file_name,"w"){|file| file.puts replace }end
Será más útil si proporciona una explicación de por qué esta es la solución preferida y explica cómo funciona. Queremos educar, no solo proporcionar código.
The Tin Man
trollop pasó a llamarse optimist github.com/manageiq/optimist . Además, es solo un analizador de opciones CLI que no se requiere realmente para responder la pregunta.
Noraj
1
Si necesita hacer sustituciones a través de los límites de las líneas, entonces el uso ruby -pi -eno funcionará porque los pprocesos de una línea a la vez. En su lugar, recomiendo lo siguiente, aunque podría fallar con un archivo de varios GB:
El está buscando un espacio en blanco (potencialmente incluyendo nuevas líneas) seguido de una cita, en cuyo caso se deshace del espacio en blanco. El %q(')es sólo una forma elegante de citar el carácter de comillas.
* .txt se puede reemplazar con otra selección o con algunos nombres de archivo o rutas
desglosado para poder explicar lo que está sucediendo pero aún ejecutable
# ARGV is an array of the arguments passed to the script.
ARGV[0..-3].each do|f|# enumerate the arguments of this script from the first to the last (-1) minus 2File.write(f,# open the argument (= filename) for writingFile.read(f)# open the argument (= filename) for reading.gsub(ARGV[-2],ARGV[-1]))# and replace all occurances of the beforelast with the last argument (string)end
EDITAR: si desea usar una expresión regular, use esto en su lugar.Obviamente, esto es solo para manejar archivos de texto relativamente pequeños, no monstruos Gigabyte
Este código no funcionará. Sugeriría probarlo antes de publicarlo, luego copiar y pegar el código de trabajo.
The Tin Man
@theTinMan Siempre pruebo antes de publicar, si es posible. Probé esto y funciona, tanto la versión corta como comentada. ¿Por qué crees que no lo haría?
peter
si te refieres al uso de una expresión regular, mira mi edición, también probado:>)
File.read
debe moderarse con la información en stackoverflow.com/a/25189286/128421 sobre por qué es malo sorber archivos grandes. Además, en lugar deFile.open(filename, "w") { |file| file << content }
utilizar variacionesFile.write(filename, content)
.Respuestas:
Descargo de responsabilidad: este enfoque es una ilustración ingenua de las capacidades de Ruby y no una solución de producción para reemplazar cadenas en archivos. Es propenso a varios escenarios de falla, como la pérdida de datos en caso de una falla, una interrupción o un disco lleno. Este código no es apto para nada más que un script rápido y único en el que se realiza una copia de seguridad de todos los datos. Por esa razón, NO copie este código en sus programas.
Aquí hay una forma rápida y corta de hacerlo.
fuente
File.write(file_name, text.gsub(/regexp/, "replace")
De hecho, Ruby tiene una función de edición in situ. Como Perl, puedes decir
Esto aplicará el código entre comillas dobles a todos los archivos del directorio actual cuyos nombres terminen con ".txt". Las copias de seguridad de los archivos editados se crearán con una extensión ".bak" (creo que "foobar.txt.bak").
NOTA: esto no parece funcionar para búsquedas de varias líneas. Para esos, debe hacerlo de la otra manera menos bonita, con un script de envoltura alrededor de la expresión regular.
fuente
<main>': undefined method
gsub 'para main: Object (NoMethodError)-i
ediciones en su lugar..bak
es la extensión utilizada para un archivo de respaldo (opcional).-p
es algo así comowhile gets; <script>; puts $_; end
. ($_
es la última línea leída, pero puede asignarle algo comoecho aa | ruby -p -e '$_.upcase!'
).Tenga en cuenta que, cuando haga esto, el sistema de archivos podría quedarse sin espacio y puede crear un archivo de longitud cero. Esto es catastrófico si está haciendo algo como escribir archivos / etc / passwd como parte de la administración de la configuración del sistema.
Tenga en cuenta que la edición de archivos en el lugar como en la respuesta aceptada siempre truncará el archivo y escribirá el nuevo archivo secuencialmente. Siempre habrá una condición de carrera en la que los lectores simultáneos verán un archivo truncado. Si el proceso se aborta por cualquier motivo (ctrl-c, asesino de OOM, caída del sistema, corte de energía, etc.) durante la escritura, el archivo truncado también quedará, lo que puede ser catastrófico. Este es el tipo de escenario de pérdida de datos que los desarrolladores DEBEN considerar porque sucederá. Por esa razón, creo que la respuesta aceptada probablemente no debería ser la respuesta aceptada. Como mínimo, escriba en un archivo temporal y mueva / cambie el nombre del archivo en su lugar como la solución "simple" al final de esta respuesta.
Necesita utilizar un algoritmo que:
Lee el archivo antiguo y escribe en el nuevo archivo. (Debe tener cuidado al absorber archivos completos en la memoria).
Cierra explícitamente el nuevo archivo temporal, que es donde puede lanzar una excepción porque los búferes de archivos no se pueden escribir en el disco porque no hay espacio. (Capture esto y limpie el archivo temporal si lo desea, pero necesita volver a lanzar algo o fallar bastante en este punto.
Corrige los permisos y modos de archivo en el nuevo archivo.
Cambia el nombre del nuevo archivo y lo coloca en su lugar.
Con los sistemas de archivos ext3, tiene la garantía de que la escritura de metadatos para mover el archivo a su lugar no será reorganizada por el sistema de archivos y escrita antes de que se escriban los búferes de datos para el nuevo archivo, por lo que esto debería tener éxito o fallar. El sistema de archivos ext4 también ha sido parcheado para admitir este tipo de comportamiento. Si está muy paranoico, debe llamar a la llamada al
fdatasync()
sistema como paso 3.5 antes de mover el archivo a su lugar.Independientemente del idioma, esta es la mejor práctica. En los lenguajes donde la llamada
close()
no arroja una excepción (Perl o C), debe verificar explícitamente el retorno declose()
y lanzar una excepción si falla.La sugerencia anterior de simplemente absorber el archivo en la memoria, manipularlo y escribirlo en el archivo garantizará la producción de archivos de longitud cero en un sistema de archivos completo. Es necesario que siempre se utilice
FileUtils.mv
para mover un archivo temporal totalmente escrito en su lugar.Una consideración final es la ubicación del archivo temporal. Si abre un archivo en / tmp, debe considerar algunos problemas:
Si / tmp está montado en un sistema de archivos diferente, puede ejecutar / tmp sin espacio antes de haber escrito el archivo que, de lo contrario, se podría implementar en el destino del archivo anterior.
Probablemente, lo que es más importante, cuando intente
mv
el archivo a través de un montaje de dispositivo, se convertirá de manera transparente encp
comportamiento. El archivo antiguo se abrirá, el inodo de archivos antiguos se conservará y se volverá a abrir y se copiará el contenido del archivo. Lo más probable es que esto no sea lo que desea y puede encontrarse con errores de "archivo de texto ocupado" si intenta editar el contenido de un archivo en ejecución. Esto también anula el propósito de usar losmv
comandos del sistema de archivos y puede ejecutar el sistema de archivos de destino sin espacio con solo un archivo escrito parcialmente.Esto tampoco tiene nada que ver con la implementación de Ruby. El sistema
mv
y loscp
comandos se comportan de manera similar.Lo que es más preferible es abrir un Tempfile en el mismo directorio que el archivo anterior. Esto asegura que no habrá problemas de movimiento entre dispositivos. El
mv
mismo nunca debería fallar, y siempre debería obtener un archivo completo y sin truncar. Cualquier falla, como dispositivo sin espacio, errores de permisos, etc., debe encontrarse durante la escritura del Tempfile.Las únicas desventajas del enfoque de crear el archivo temporal en el directorio de destino son:
Aquí hay un código que implementa el algoritmo completo (el código de Windows no está probado y sin terminar):
Y aquí hay una versión un poco más ajustada que no se preocupa por cada caso de borde posible (si está en Unix y no le importa escribir en / proc):
El caso de uso realmente simple, para cuando no le importan los permisos del sistema de archivos (o no se está ejecutando como root o se está ejecutando como root y el archivo es propiedad de root):
TL; DR : Debe usarse en lugar de la respuesta aceptada como mínimo, en todos los casos, para garantizar que la actualización sea atómica y que los lectores simultáneos no vean archivos truncados. Como mencioné anteriormente, crear el Tempfile en el mismo directorio que el archivo editado es importante aquí para evitar que las operaciones mv entre dispositivos se traduzcan en operaciones cp si / tmp está montado en un dispositivo diferente. Llamar a fdatasync es una capa adicional de paranoia, pero generará un impacto en el rendimiento, por lo que lo omití de este ejemplo ya que no se practica comúnmente.
fuente
Realmente no hay una forma de editar archivos in situ. Lo que suele hacer cuando puede salirse con la suya (es decir, si los archivos no son demasiado grandes) es leer el archivo en la memoria (
File.read
), realizar sus sustituciones en la cadena de lectura (String#gsub
) y luego escribir la cadena cambiada de nuevo en la archivo (File.open
,File#write
).Si los archivos son lo suficientemente grandes como para que eso sea inviable, lo que debe hacer es leer el archivo en fragmentos (si el patrón que desea reemplazar no abarca varias líneas, entonces un fragmento generalmente significa una línea; puede usar
File.foreach
para leer un archivo línea por línea), y para cada fragmento, realice la sustitución y añádalo a un archivo temporal. Cuando haya terminado de iterar sobre el archivo fuente, ciérrelo y utiliceFileUtils.mv
para sobrescribirlo con el archivo temporal.fuente
Otro enfoque es usar la edición in situ dentro de Ruby (no desde la línea de comando):
Si no desea crear una copia de seguridad, cambie
'.bak'
a''
.fuente
read
) el archivo. Es escalable y debería ser muy rápido.Esto funciona para mi:
fuente
Aquí hay una solución para buscar / reemplazar en todos los archivos de un directorio determinado. Básicamente tomé la respuesta proporcionada por sepp2k y la expandí.
fuente
fuente
Si necesita hacer sustituciones a través de los límites de las líneas, entonces el uso
ruby -pi -e
no funcionará porque losp
procesos de una línea a la vez. En su lugar, recomiendo lo siguiente, aunque podría fallar con un archivo de varios GB:El está buscando un espacio en blanco (potencialmente incluyendo nuevas líneas) seguido de una cita, en cuyo caso se deshace del espacio en blanco. El
%q(')
es sólo una forma elegante de citar el carácter de comillas.fuente
Aquí una alternativa a la única línea de Jim, esta vez en un guión
Guárdelo en un script, por ejemplo, replace.rb
Empiezas en la línea de comando con
* .txt se puede reemplazar con otra selección o con algunos nombres de archivo o rutas
desglosado para poder explicar lo que está sucediendo pero aún ejecutable
EDITAR: si desea usar una expresión regular, use esto en su lugar.Obviamente, esto es solo para manejar archivos de texto relativamente pequeños, no monstruos Gigabyte
fuente