Línea de comando: busque y reemplace en todos los nombres de archivo que coincidan con grep

80

Estoy tratando de buscar y reemplazar una cadena en todos los archivos que coinciden con grep:

grep -n 'foo' * me dará salida en la forma:

[filename]:[line number]:[text]

Para cada archivo devuelto por grep, me gustaría modificar el archivo reemplazándolo foocon bar.

Michael Kristofik
fuente

Respuestas:

70

¿Te refieres a buscar y reemplazar una cadena en todos los archivos que coincidan con grep?

perl -p -i -e 's/oldstring/newstring/g' `grep -ril searchpattern *`

Editar

Dado que esta parece ser una pregunta bastante popular, pensé que actualizaría.

Hoy en día lo uso principalmente ack-grepporque es más fácil de usar. Entonces el comando anterior sería:

perl -p -i -e 's/old/new/g' `ack -l searchpattern`

Para manejar los espacios en blanco en los nombres de los archivos, puede ejecutar:

ack --print0 -l searchpattern | xargs -0 perl -p -i -e 's/old/new/g'

puedes hacer más con ack-grep. Supongamos que desea restringir la búsqueda solo a archivos HTML:

ack --print0 --html -l searchpattern | xargs -0 perl -p -i -e 's/old/new/g'

Y si el espacio en blanco no es un problema, es aún más corto:

perl -p -i -e 's/old/new/g' `ack -l --html searchpattern`
perl -p -i -e 's/old/new/g' `ack -f --html` # will match all html files
armandino
fuente
3
Creo que esto podría tener un problema con los archivos / carpetas que contienen espacios. Can't open Untitled: No such file or directory, <> line 5al intentar "Carpeta sin título / archivo.txt".
Xeoncross
108

Esto parece ser lo que desea, según el ejemplo que dio:

sed -i 's/foo/bar/g' *

No es recursivo (no descenderá a subdirectorios). Para una buena solución reemplazando archivos seleccionados a lo largo de un árbol, usaría find:

find . -name '*.html' -print -exec sed -i.bak 's/foo/bar/g' {} \;

La *.htmles la expresión que los archivos deben coincidir, .bakdespués de que -ihace una copia del archivo original, con una extensión .bak (puede ser cualquier extensión que desee) y gal final de la expresión sed le dice a sed que reemplace múltiples copias en una línea (en lugar de solo la primera). La función -printde buscar es una conveniencia para mostrar qué archivos se estaban haciendo coincidir. Todo esto depende de las versiones exactas de estas herramientas en su sistema.

MattJ
fuente
1
Una advertencia para los usuarios de cygwin. El combo find y sed parece cambiar los derechos de usuario de los archivos que se transmiten. Esto se puede arreglar simplemente usando el comando chmod -R 644 * del mismo nivel de directorio que se usó cuando se utilizó find / sed.
kaskelotti
Una palabra de advertencia a las personas que no quieran utilizar el argumento -i: si no lo usa, no funciona (no me pregunten por qué)
knocte
4
@knocte -i le dice a sed que modifique el archivo; de lo contrario, solo imprime la versión modificada en stdout. Si no desea que se cree el archivo .bak, simplemente omita la parte '.bak', -i también funciona de forma independiente.
MattJ
2
En OSX, debe asignar al findcomando un directorio desde el que comenzar, por ejemplo find . -name '*.html'o find directoryname/ -name '*'.
Michiel Kauw-A-Tjoe
Necesitaba agregar una -e, de lo contrario, pensó que la parte 's ...' era el sufijosed -ie 's/foo/bar/g' *
vish
14

Si sed(1)tiene una -iopción, úsela así:

for i in *; do
  sed -i 's/foo/bar/' $i
done

Si no es así, hay varias formas de variaciones en lo siguiente según el idioma con el que quieras jugar:

ruby -i.bak -pe 'sub(%r{foo}, 'bar')' *
perl -pi.bak -e 's/foo/bar/' *
Keltia
fuente
2
for i in *; do ...es redundante, sed puede tomar una lista de archivos como argumentos.
Jens
2
@Jens, ¿por qué no mejorar esta respuesta agregando su propio ejemplo al anterior?
Urraca
Las variables siempre deben estar entre comillasfor i in *; do; sed -i 's/foo/bar/g' "$i"; done
cat
6

Me gusta y utilicé la solución anterior o una búsqueda y reemplazo en todo el sistema entre miles de archivos:

find -name '*.htm?' -print -exec sed -i.bak 's/foo/bar/g' {} \;

Supongo que con el '* .htm?' en lugar de .html, busca y encuentra archivos .htm y .html por igual.

Reemplazo el .bak con la tilde (~) más utilizada en el sistema para facilitar la limpieza de los archivos de respaldo.

Hans
fuente
4

Esto funciona usando grep sin necesidad de usar perl o find.

grep -rli 'old-word' * | xargs -i@ sed -i 's/old-word/new-word/g' @
Pymarco
fuente
xargs no tiene una -i en OSX o BSD openbsd.org/cgi-bin/man.cgi/OpenBSD-current/man1/… ¿ Querías usar una "I" mayúscula?
Tony Adams
No sabía -ique no funcionaba para otros sistemas operativos. Funciona para mí en ubuntu.
pymarco
Mi página de xargsmanual (en Ubuntu) dice que -iestá en desuso y que se debe usar -Ien su lugar. Entonces, deberíamos decir:grep -rli 'old-word' * | xargs -I filepath sed -i 's/old-word/new-word/g' filepath
Max Wallace
3

find . -type f -print0 | xargs -0 <sed/perl/ruby cmd>procesará múltiples nombres de archivos contenidos en el espacio a la vez, cargando un intérprete por lote. Mucho mas rápido.

koolb
fuente
@knocte, "cmd" es un token para todo el comando de búsqueda y reemplazo para cualquier herramienta que se elija. Esta respuesta responde a la pregunta sobre cómo lidiar con los nombres de archivo contenidos en espacios en blanco.
Tony Adams
1

La respuesta ya dada de usar find y sed

find -name '*.html' -print -exec sed -i.bak 's/foo/bar/g' {} \;

es probablemente la respuesta estándar. O puede usar en perl -pi -e s/foo/bar/g'lugar del sedcomando.

Para los usos más rápidos, puede encontrar que el comando rpl es más fácil de recordar. Aquí está el reemplazo (foo -> bar), recursivamente en todos los archivos en el directorio actual:

rpl -R foo bar .

No está disponible de forma predeterminada en la mayoría de las distribuciones de Linux, pero se instala rápidamente ( apt-get install rplo similar).

Sin embargo, para trabajos más difíciles que involucran expresiones regulares y sustitución hacia atrás, o cambios de nombre de archivos, así como buscar y reemplazar, la herramienta más general y poderosa que conozco es repren , un pequeño script de Python que escribí hace un tiempo para algunos tareas de refactorización y cambio de nombre más complicadas. Las razones por las que podría preferirlo son:

  • Admite el cambio de nombre de archivos, así como la búsqueda y reemplazo en el contenido del archivo (incluido el movimiento de archivos entre directorios y la creación de nuevos directorios principales).
  • Vea los cambios antes de comprometerse a realizar la búsqueda y reemplazo.
  • Admite expresiones regulares con sustitución inversa, palabras completas, no distingue entre mayúsculas y minúsculas y preservación de mayúsculas y minúsculas (reemplace foo -> bar, Foo -> Bar, FOO -> BAR).
  • Funciona con múltiples reemplazos, incluidos intercambios (foo -> bar y bar -> foo) o conjuntos de reemplazos no únicos (foo -> bar, f -> x).

Consulte el archivo README para ver ejemplos.

jlevy
fuente
1

En realidad, esto es más fácil de lo que parece.

grep -Rl 'foo' ./ | xargs -n 1 -I % sh -c "ls %; sed -i 's/foo/bar/g' %";
  • grep se repite a través de su árbol (-R) e imprime solo el nombre del archivo (-l), comenzando en el directorio actual (./)
  • que se canaliza a xargs, que los procesa uno a la vez (-n 1) y usa% como marcador de posición (-I%) en un comando de shell (sh -c)
  • en el comando de shell, primero se imprime el nombre del archivo (ls%;)
  • luego sed hace una operación en línea (-i), una sustitución ('s /') de foo con bar (foo / bar), globalmente (/ g) en el archivo (nuevamente, representado por%)

Pan comido. Si comprende bien find, grep, xargs, sed y awk, casi nada es imposible cuando se trata de la manipulación de archivos de texto en bash :)

Siliconrockstar
fuente
Bah, indiferente, este pymarco ya cubrió esto arriba. Dejando mi respuesta para la explicación.
siliconrockstar