¿Cómo puedo hacer una búsqueda y reemplazo recursivo desde la línea de comandos?

84

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.

Nathan Long
fuente
Puede encontrar una respuesta alternativa para las mismas preguntas aquí stackoverflow.com/questions/9704020/…
dunxd
Puede ser una buena idea probar esto en vim. De esa manera, puede usar la función de confirmación para asegurarse de no intercambiar algo que no tiene la intención. No estoy seguro de si se puede hacer en todo el directorio.
Samy Bencherif

Respuestas:

106

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:

  1. find . -type f -name '*.txt'encuentra, en el directorio actual ( .) y debajo, todos los archivos regulares ( -type f) cuyos nombres terminan en.txt
  2. | pasa la salida de ese comando (una lista de nombres de archivo) al siguiente comando
  3. xargs recoge esos nombres de archivo y los entrega uno por uno para sed
  4. 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.

Nathan Long
fuente
99
Nunca jamás se encuentre una salida de tubería a xargs sin la -print0opción. Su comando fallará en archivos con espacios, etc. en su nombre.
slhck
20
Además, solo find -name '*.txt' -exec sed -i 's/foo/bar/g' {} +haré todo esto con GNU find.
Daniel Andersson
66
Me sale sed: can't read : No such file or directorycuando corro find . -name '*.md' -print0 | xargs -0 sed -i '' -e 's/ä/ä/g', pero find . -name '*.md' -print0da una lista de muchos archivos.
Martin Thoma
99
Esto funciona para mí si me quito el espacio entre el -iy el''
canadiense Lucas REINSTATE MONICA
3
¿Cuál es el significado de ''después de sed -icuál es el ''papel?
Jas
27
find . -type f -name "*.txt" -exec sed -i'' -e 's/foo/bar/g' {} +

Esto elimina la xargsdependencia.

Ztyx
fuente
44
Esto no funciona con GNU sed, por lo que fallará en la mayoría de los sistemas. GNU sedrequiere que no pongas espacio entre -iy ''.
slhck
1
Las respuestas aceptadas lo explican mejor. pero +1 por usar la sintaxis correcta.
orodbhen
9

Si está utilizando Git, puede hacer esto:

git grep -lz foo | xargs -0 sed -i '' -e 's/foo/bar/g'

-lenumera solo nombres de archivos. -zimprime 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. 🙂)

David Winiecki
fuente
1
Gran +1 para esta solución. El resto de las 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 grepse ilumina rápido en comparación.
Josh Kupershmidt
3

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)

Brian Agnew
fuente
2

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"

nexayq
fuente
Pruébelo cuando tenga archivos con espacios en sus nombres. (Hay una regla general que dice: "Si algo parece demasiado bueno para ser verdad, probablemente lo sea". Si ha "descubierto" una solución más compacta que cualquier otra que haya publicado otra persona en 3 años y medio, debe preguntarse por qué eso podría ser.)
Scott
1

Utilice este script de shell

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 changeen una carpeta en mi $PATHy 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
Nathan Long
fuente
0

Mi caso de uso era lo que quería para reemplazar foo:/Drive_Lettercon 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.

neo7
fuente
0

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/'
John Morgan
fuente
El hecho de que cambies -ia -i ''es probablemente más relevante que el hecho de que cambiaste -execa -print0 | xargs -0. Por cierto, probablemente no necesites el -e.
Scott, el
0
# 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 -Lbandera. 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'
sMajeed
fuente