Usando un shell como bash o zshell, ¿cómo puedo hacer un 'buscar y reemplazar' recursivo? En otras palabras, quiero reemplazar cada aparición de 'foo' con 'bar' en todos los archivos en este directorio y sus subdirectorios.
Usando un shell como bash o zshell, ¿cómo puedo hacer un 'buscar y reemplazar' recursivo? En otras palabras, quiero reemplazar cada aparición de 'foo' con 'bar' en todos los archivos en este directorio y sus subdirectorios.
Respuestas:
Este comando lo hará (probado en Mac OS X Lion y Kubuntu Linux).
# Recursively find and replace in files
find . -type f -name "*.txt" -print0 | xargs -0 sed -i '' -e 's/foo/bar/g'
Así es como funciona:
find . -type f -name '*.txt'
encuentra, en el directorio actual ( .
) y debajo, todos los archivos regulares ( -type f
) cuyos nombres terminan en.txt
|
pasa la salida de ese comando (una lista de nombres de archivo) al siguiente comandoxargs
recoge esos nombres de archivo y los entrega uno por uno para sed
sed -i '' -e 's/foo/bar/g'
significa "editar el archivo en su lugar, sin una copia de seguridad, y realizar la siguiente sustitución ( s/foo/bar
) varias veces por línea ( /g
)" (ver man sed
)Tenga en cuenta que la parte 'sin respaldo' en la línea 4 está bien para mí, porque los archivos que estoy cambiando están bajo control de versión de todos modos, por lo que puedo deshacer fácilmente si hubo un error.
-print0
opción. Su comando fallará en archivos con espacios, etc. en su nombre.
find -name '*.txt' -exec sed -i 's/foo/bar/g' {} +
haré todo esto con GNU find.
sed: can't read : No such file or directory
cuando corro find . -name '*.md' -print0 | xargs -0 sed -i '' -e 's/ä/ä/g'
, pero find . -name '*.md' -print0
da una lista de muchos archivos.
-i
y el''
''
después de sed -i
cuál es el ''
papel?
find . -type f -name "*.txt" -exec sed -i'' -e 's/foo/bar/g' {} +
Esto elimina la xargs
dependencia.
sed
, por lo que fallará en la mayoría de los sistemas. GNU sed
requiere que no pongas espacio entre -i
y ''
.
Si está utilizando Git, puede hacer esto:
git grep -lz foo | xargs -0 sed -i '' -e 's/foo/bar/g'
-l
enumera solo nombres de archivos. -z
imprime un byte nulo después de cada resultado.
Terminé haciendo esto porque algunos archivos en un proyecto no tenían una nueva línea al final del archivo, y agregué una nueva línea incluso cuando no hizo otros cambios. (Sin comentarios sobre si los archivos deberían tener una nueva línea al final. 🙂)
find ... -print0 | xargs -0 sed ...
soluciones no solo toman mucho más tiempo, sino que también agregan nuevas líneas a los archivos que aún no tienen una, lo cual es una molestia cuando se trabaja dentro de un repositorio git. git grep
se ilumina rápido en comparación.
Aquí está mi función zsh / perl que uso para esto:
change () {
from=$1
shift
to=$1
shift
for file in $*
do
perl -i.bak -p -e "s{$from}{$to}g;" $file
echo "Changing $from to $to in $file"
done
}
Y lo ejecutaría usando
$ change foo bar **/*.java
(por ejemplo)
Tratar:
sed -i 's/foo/bar/g' $(find . -type f)
Probado en Ubuntu 12.04.
Este comando NO funcionará si los nombres de subdirectorios y / o nombres de archivos contienen espacios, pero si los tiene, no use este comando ya que no funcionará.
En general, es una mala práctica usar espacios en los nombres de directorio y de archivo.
http://linuxcommand.org/lc3_lts0020.php
Mire "Datos importantes sobre nombres de archivos"
Ahora uso este script de shell, que combina cosas que aprendí de las otras respuestas y de la búsqueda en la web. Lo puse en un archivo llamado change
en una carpeta en mi $PATH
y lo hice chmod +x change
.
#!/bin/bash
function err_echo {
>&2 echo "$1"
}
function usage {
err_echo "usage:"
err_echo ' change old new foo.txt'
err_echo ' change old new foo.txt *.html'
err_echo ' change old new **\*.txt'
exit 1
}
[ $# -eq 0 ] && err_echo "No args given" && usage
old_val=$1
shift
new_val=$1
shift
files=$* # the rest of the arguments
[ -z "$old_val" ] && err_echo "No old value given" && usage
[ -z "$new_val" ] && err_echo "No new value given" && usage
[ -z "$files" ] && err_echo "No filenames given" && usage
for file in $files; do
sed -i '' -e "s/$old_val/$new_val/g" $file
done
Mi caso de uso era lo que quería para reemplazar
foo:/Drive_Letter
con foo:/bar/baz/xyz
En mi caso tuve la oportunidad de hacerlo con el siguiente código. Estaba en la misma ubicación del directorio donde había gran cantidad de archivos.
find . -name "*.library" -print0 | xargs -0 sed -i '' -e 's/foo:\/Drive_Letter:/foo:\/bar\/baz\/xyz/g'
Espero que haya ayudado.
El siguiente comando funcionó bien en Ubuntu y CentOS; sin embargo, bajo OS XI seguía recibiendo errores:
find . -name Root -exec sed -i 's/1.2.3.4\/home/foo.com\/mnt/' {} \;
sed: 1: "./Root": código de comando no válido.
Cuando intenté pasar los parámetros a través de xargs funcionó bien sin errores:
find . -name Root -print0 | xargs -0 sed -i '' -e 's/1.2.3.4\/home/foo.com\/mnt/'
-i
a -i ''
es probablemente más relevante que el hecho de que cambiaste -exec
a -print0 | xargs -0
. Por cierto, probablemente no necesites el -e
.
# Recursively find and replace in files
find . -type f -name "*.txt" -print0 | xargs -0 sed -i '' -e 's/foo/bar/g'
Lo anterior funcionó a las mil maravillas, pero con los directorios vinculados, debo agregarle una -L
bandera. La versión final se ve así:
# Recursively find and replace in files
find -L . -type f -name "*.txt" -print0 | xargs -0 sed -i '' -e 's/foo/bar/g'