¿Cómo hacer una búsqueda / reemplazo recursivo de una cadena con awk o sed?


679

¿Cómo encuentro y reemplazo cada aparición de:

subdomainA.example.com

con

subdomainB.example.com

en cada archivo de texto bajo el /home/www/árbol de directorios de forma recursiva?


93
Consejo: No haga lo siguiente en un árbol de pago de svn ... sobrescribirá los archivos de la carpeta mágica .svn.
J. Polfer

77
Dios mío, esto es exactamente lo que acabo de hacer. Pero funcionó y no parece haber hecho ningún daño. ¿Que es lo peor que puede pasar?
J. Katzwinkel

55
@ J.Katzwinkel: al menos, puede corromper las sumas de verificación, lo que puede corromper su repositorio.
ninjagecko

3
Consejo rápido para todas las personas que usan sed: agregará nuevas líneas finales a sus archivos. Si no los quiere, primero haga un find-replace que no coincida con nada, y comprométalo a git. Entonces haz el verdadero. Luego, vuelva a crear una base interactiva y elimine el primero.
funroll

55
Puede excluir un directorio, como git, a partir de los resultados mediante el uso -path ./.git -prune -ode find . -path ./.git -prune -o -type f -name '*matchThisText*' -print0antes del conexionado a xargs
devinbost

Respuestas:


851
find /home/www \( -type d -name .git -prune \) -o -type f -print0 | xargs -0 sed -i 's/subdomainA\.example\.com/subdomainB.example.com/g'

-print0le dice findque imprima cada uno de los resultados separados por un carácter nulo, en lugar de una nueva línea. En el caso improbable de que su directorio tenga archivos con nuevas líneas en los nombres, esto todavía permite xargstrabajar con los nombres de archivo correctos.

\( -type d -name .git -prune \)es una expresión que omite completamente todos los directorios nombrados .git. Puede expandirlo fácilmente, si usa SVN o tiene otras carpetas que desea preservar, solo haga coincidir con más nombres. Es más o menos equivalente a -not -path .git, pero más eficiente, porque en lugar de verificar cada archivo en el directorio, lo omite por completo. El -oafter se requiere debido a cómo -prunefunciona realmente.

Para más información, ver man find.


132
En OSX puede encontrar sed: 1: "...": invalid command code .problemas. Parece que la opción -i espera una extensión y analiza el 's/../...'comando. Solución: pase la extensión '' a la opción -i como sed -i '' 's/....
Robert Lujo

66
Nota: si usa esto sobre un directorio y se pregunta por qué svn stno muestra cambios, ¡es porque también ha modificado los archivos en los directorios .svn! Usar en su find . -maxdepth 1 -type f -print0 | xargs -0 sed -i 's/toreplace/replaced/g'lugar.
ACK_stoverflow

57
Además, tenga cuidado si está en un repositorio git. Pensé que era inteligente al probar esto en una rama clara para poder revertir si hacía algo malo, pero en cambio corrompió mi índice git.
Ciryon

13
Use esto grep -r 'hello' -l --null . | xargs -0 sed -i 's#hello#world#g'para evitar editar archivos no relacionados (sed podría cambiar la codificación del archivo).
caiguanhao

66
"pero en su lugar corrompió mi índice git". No se preocupe demasiado por esto, solo puede hacer find .git ... | ... 'sed -i s/(the opposite from before)/g'para corregir su índice git
Massey101

259

Nota : No ejecute este comando en una carpeta que incluya un repositorio git; los cambios en .git podrían dañar su índice git.

find /home/www/ -type f -exec \
    sed -i 's/subdomainA\.example\.com/subdomainB.example.com/g' {} +

En comparación con otras respuestas aquí, esto es más simple que la mayoría y usa sed en lugar de perl, que es lo que preguntó la pregunta original.


50
Tenga en cuenta que si está utilizando BSD sed (incluso en Mac OS X) deberá proporcionar una arg de cadena vacía explícita a la -iopción de sed . es decir: sed -i '' 's/original/replacement/g'
Nathan Craike el

2
@JohnZwinck Mi error, perdí el +. Aunque parezca extraño, la solución de Nikita es más rápida para mí.
Sam

66
@AoeAoe: +Reduce en gran medida el número de sedprocesos generados. Es mas eficiente.
John Zwinck

44
¿Cómo puedo hacer esto de manera segura en una carpeta con un repositorio git?
Hatshepsut

20
Es seguro para ejecutar en una carpeta que contiene un repositorio git si se excluye la cesión temporal de los resultados de la Categoría: find . -not -path '*/\.git*' -type f ....
Dale Anderson

213

La forma más simple para mí es

grep -rl oldtext . | xargs sed -i 's/oldtext/newtext/g'

1
@Anatoly: solo una pregunta: ¿cómo puedo excluir archivos binarios (archivos ejecutables) ?
user2284570

3
@ user2284570 Utilice los indicadores -Io --binary-file=without-matchgrep.
Zéychin

34
Esto funciona especialmente bien, cuando necesita excluir directorios, como con .svn. Por ejemplo:grep -rl oldtext . --exclude-dir=.svn | xargs sed -i 's/oldtext/newtext/g'
phyatt

11
brew install gnu-sedy usar gseden OSX para evitar un mundo de dolor.
P i

1
chicos por favor atención, si su proyecto es git versionado, use en su lugar: git grep -rl oldtext . | xargs sed -i 's/oldtext/newtext/g'. no es nada agradable joder tu .gitdirectorio
Paolo

61

Todos los trucos son casi iguales, pero me gusta este:

find <mydir> -type f -exec sed -i 's/<string1>/<string2>/g' {} +
  • find <mydir>: busca en el directorio.

  • -type f:

    El archivo es de tipo: archivo normal

  • -exec command {} +:

    Esta variante de la acción -exec ejecuta el comando especificado en los archivos seleccionados, pero la línea de comando se crea agregando cada nombre de archivo seleccionado al final; El número total de invocaciones del comando será mucho menor que el número de archivos coincidentes. La línea de comando se construye de la misma manera que xargs construye sus líneas de comando. Solo se permite una instancia de `{} 'dentro del comando. El comando se ejecuta en el directorio de inicio.


@ user2284570 con -exec? Intente establecer la ruta al ejecutable en lugar del nombre de una herramienta.
I159

@ I159: No: excluir archivos binarios ejecutables (pero incluir scripts de shell) .
user2284570

8
@ I159 ¿No es esta respuesta idéntica a la de John Zwinck ?
Vuelva a instalar Monica Please

1
@ user2284570 El concepto de un "archivo binario" no está completamente bien definido. Puede usar el filecomando para intentar determinar el tipo de cada archivo, pero las variaciones accidentales en su salida pueden ser un poco desconcertantes. La opción -I(aka --mime) ayuda un poco, o --mime-typesi la tiene. Lamentablemente, cómo refactorizar esta simple línea para hacer esto está lamentablemente fuera del alcance de este pequeño cuadro de comentarios. Tal vez publicar una pregunta por separado si necesita ayuda? (Tal vez agregue un comentario con un enlace aquí.)
tripleee

1
La respuesta más limpia! gracias compañero
jukerok

39
cd /home/www && find . -type f -print0 |
  xargs -0 perl -i.bak -pe 's/subdomainA\.example\.com/subdomainB.example.com/g'

2
Tengo curiosidad, ¿hay alguna razón para usar -print0y en xargslugar de -execo -execdir?
Philipp

44
Hay: de "man find": el comando especificado se ejecuta una vez para cada archivo coincidente. Es decir, si hay 2000 archivos en / home / www, entonces 'find ... -exec ...' generará 2000 invocaciones de perl; mientras que 'encontrar ... | xargs ... 'solo invocará perl una o dos veces (suponiendo ARG_MAX de aproximadamente 32K y una longitud promedio de nombre de archivo de 20).
Empleado ruso

2
@ Ruso empleado: es por eso que usarías: find -exec command {} +evita invocaciones excesivas del comando como xargs, pero sin el proceso por separado.
John Zwinck

2
¿En qué plataforma? La solución xargs es portátil, las invocaciones "mágicas" de "find ... -exec" que no invocan un subproceso por cada archivo encontrado no lo son.
Empleado ruso

44
@EmployedRussian, find -exec ... {} +se ha especificado en POSIX desde 2006.
Charles Duffy

34

Para mí, la solución más fácil de recordar es https://stackoverflow.com/a/2113224/565525 , es decir:

sed -i '' -e 's/subdomainA/subdomainB/g' $(find /home/www/ -type f)

NOTA : -i ''resuelve el problema de OSXsed: 1: "...": invalid command code .

NOTA : Si hay demasiados archivos para procesar, obtendrá Argument list too long. La solución alternativa: uso find -execo xargssolución descrita anteriormente.


44
El workarounddebe ser la sintaxis preferida en todos los casos.
Vuelva a instalar Monica Please

1
El problema con la sustitución de comandos $(find...)es que no hay forma de que el shell maneje los nombres de archivo con espacios en blanco u otros metacaracteres del shell en ellos. Si sabe que esto no es un problema, este enfoque está bien; pero hemos tenido demasiadas preguntas en las que las personas no fueron advertidas sobre este problema o no entendieron la advertencia.
tripleee

30

Para cualquiera que use el buscador plateado ( ag)

ag SearchString -l0 | xargs -0 sed -i 's/SearchString/Replacement/g'

Dado que ag ignora los archivos / carpetas git / hg / svn de forma predeterminada, es seguro ejecutarlo dentro de un repositorio.


16

Un buen oneliner como extra. Usando git grep.

git grep -lz 'subdomainA.example.com' | xargs -0 perl -i'' -pE "s/subdomainA.example.com/subdomainB.example.com/g"

3
Buena idea si trabaja dentro de un repositorio de git, ya que no corre el riesgo de sobrescribir .git / contenidos (como se informa en los comentarios a otra respuesta).
mahemoff

1
Gracias, lo uso como una función bash refactor() { echo "Replacing $1 by $2 in all files in this git repository." git grep -lz $1| xargs -0 perl -i'' -pE "s/$1/$2/g" }Uso, por ejemplo, para reemplazar 'palabra' con 'espada': refactor word swordluego verifique con qué hizo git diff.
Paul Rougieux

16

Para reducir los archivos de forma recursiva sed, podría greppara su instancia de cadena:

grep -rl <oldstring> /path/to/folder | xargs sed -i s^<oldstring>^<newstring>^g

Si ejecuta man grep, notará que también puede definir un--exlude-dir="*.git" indicador si desea omitir la búsqueda a través de directorios .git, evitando problemas de índice git como otros han señalado cortésmente.

Llevándote a:

grep -rl --exclude-dir="*.git" <oldstring> /path/to/folder | xargs sed -i s^<oldstring>^<newstring>^g

13

Este es compatible con repositorios git, y un poco más simple:

Linux:

git grep -l 'original_text' | xargs sed -i 's/original_text/new_text/g'

Mac:

git grep -l 'original_text' | xargs sed -i '' -e 's/original_text/new_text/g'

(Gracias a http://blog.jasonmeridth.com/posts/use-git-grep-to-replace-strings-in-files-in-your-git-repository/ )


Más sabio usar git-grepla -zopción junto con xargs -0.
gniourf_gniourf

git grepobviamente solo tiene sentido en un gitrepositorio. El reemplazo general sería grep -r.
tripleee

@gniourf_gniourf ¿Puedes explicarlo?
Petr Peller

2
@PetrPeller: with -z, git-grepseparará los campos de salida por bytes nulos en lugar de líneas nuevas; y con -0, xargsleerá la entrada separada por bytes nulos, en lugar de espacios en blanco (y no hará cosas raras con comillas). Así que si usted no quiere que la orden de descanso si los nombres de archivo contienen espacios, citas u otros caracteres extraños, el comando es: git grep -z -l 'original_text' | xargs -0 sed ....
gniourf_gniourf

10
find /home/www/ -type f -exec perl -i.bak -pe 's/subdomainA\.example\.com/subdomainB.example.com/g' {} +

find /home/www/ -type f enumerará todos los archivos en / home / www / (y sus subdirectorios). El indicador "-exec" le dice a find que ejecute el siguiente comando en cada archivo encontrado.

perl -i.bak -pe 's/subdomainA\.example\.com/subdomainB.example.com/g' {} +

es el comando que se ejecuta en los archivos (muchos a la vez). El {}se reemplaza por nombres de archivo. Al +final del comando le indica findque cree un comando para muchos nombres de archivo.

Por el find página de manual: "La línea de comando se construye de la misma manera que xargs construye sus líneas de comando".

Por lo tanto, es posible lograr su objetivo (y manejar nombres de archivos que contienen espacios) sin usar xargs -0, o -print0.


8

Solo necesitaba esto y no estaba contento con la velocidad de los ejemplos disponibles. Entonces se me ocurrió el mío:

cd /var/www && ack-grep -l --print0 subdomainA.example.com | xargs -0 perl -i.bak -pe 's/subdomainA\.example\.com/subdomainB.example.com/g'

Ack-grep es muy eficiente para encontrar archivos relevantes. Este comando reemplazó ~ 145 000 archivos con una brisa, mientras que otros tardaron tanto que no podía esperar hasta que terminaran.


Agradable, pero grep -ril 'subdomainA' *no es tan rápido como grep -Hr 'subdomainA' * | cut -d: -f1.
trusktr

@Henno: solo una pregunta: ¿cómo puedo excluir archivos binarios (archivos ejecutables) ?
user2284570

ack-grep lo hace automáticamente por ti.
Henno

@Henno: ¿Incluye scripts de shell?
user2284570

Si. Aquí hay una lista completa de los tipos de archivos que admite: beyondgrep.com/documentation
Henno

6

Un método sencillo si necesita excluir directorios ( --exclude-dir=.svn) y también puede tener nombres de archivo con espacios (usando 0Byte con grep -Zyxargs -0

grep -rlZ oldtext . --exclude-dir=.svn | xargs -0 sed -i 's/oldtext/newtext/g'

6

La forma más simple de reemplazar ( todos los archivos, directorio, recursivo )

find . -type f -not -path '*/\.*' -exec sed -i 's/foo/bar/g' {} +

Nota: a veces es posible que deba ignorar algunos archivos ocultos, es decir .git, puede usar el comando anterior.

Si desea incluir el uso de archivos ocultos,

find . -type f  -exec sed -i 's/foo/bar/g' {} +

En ambos casos, la cadena foose reemplazará por una nueva.bar


5

grep -lr 'subdomainA.example.com' | while read file; do sed -i "s/subdomainA.example.com/subdomainB.example.com/g" "$file"; done

Supongo que la mayoría de las personas no saben que pueden canalizar algo en un "archivo mientras se lee" y evita esos desagradables argumentos -print0, mientras preseleccionan espacios en los nombres de archivo.

Además, agregar un echoantes del sed le permite ver qué archivos cambiarán antes de hacerlo realmente.


La razón -print0es útil porque maneja casos que while readsimplemente no pueden manejar: una nueva línea es un carácter válido en un nombre de archivo Unix, por lo que para que su código sea completamente robusto, también necesita hacer frente a dichos nombres de archivo. (Además, desea read -revitar un comportamiento legado molesto de POSIX en read.)
tripleee

Además, sedes un no-op si no hay coincidencias, por lo grepque no es realmente necesario; aunque es una optimización útil para evitar reescribir archivos que no contienen coincidencias, si tiene muchos de esos, o si desea evitar actualizar innecesariamente los sellos de fecha en los archivos.
tripleee

5

Puede usar awk para resolver esto de la siguiente manera,

for file in `find /home/www -type f`
do
   awk '{gsub(/subdomainA.example.com/,"subdomainB.example.com"); print $0;}' $file > ./tempFile && mv ./tempFile $file;
done

Espero que esto te ayudará !!!


Funciona en MacOs sin ningún problema! Todos los sedcomandos basados ​​fallaron cuando se incluyeron archivos binarios incluso con la configuración específica de osx.
Jankapunkt

Cuidado ... esto explotará si alguno de los archivos finddevueltos tiene un espacio en sus nombres. Es mucho más seguro de usar while read: stackoverflow.com/a/9612560/1938956
Soren Bjornstad

4

Prueba esto:

sed -i 's/subdomainA/subdomainB/g' `grep -ril 'subdomainA' *`

1
Hola @RikHic, buen consejo: estaba pensando en algo como esto; desafortunadamente, el formato anterior no resultó bien :) Así que intentaré con una etiqueta previa (no funciona), por lo que con escapes de retroceso, entonces: sed -i 's/subdomainA/subdomainB/g'` grep -ril 'subdomainA' /home/www/*` - esto todavía no se ve muy bien, pero debería sobrevivir copypaste :) ¡Salud!
sdaau

4
#!/usr/local/bin/bash -x

find * /home/www -type f | while read files
do

sedtest=$(sed -n '/^/,/$/p' "${files}" | sed -n '/subdomainA/p')

    if [ "${sedtest}" ]
    then
    sed s'/subdomainA/subdomainB/'g "${files}" > "${files}".tmp
    mv "${files}".tmp "${files}"
    fi

done

4

Según esta publicación de blog:

find . -type f | xargs perl -pi -e 's/oldtext/newtext/g;'

¿Cómo escapas de los cortes /? Por ejemplo, quiero reemplazar las direcciones IP: xxx.xxx.xxx.xxxparaxxx.xxx.xxx.xxx/folder
Pathros

Puedes escapar de /con \. Por ejemplo:find . -type f | xargs perl -pi -e 's/xxx.xxx.xxx.xxx\/folder/newtext/g;'
J.Hpour

3

Si no le importa usar vimjunto con grepo findherramientas, puede seguir la respuesta dada por el usuario Gert en este enlace -> ¿Cómo hacer un reemplazo de texto en una jerarquía de carpetas grandes?.

Aquí está el trato:

  • grep recursivamente para la cadena que desea reemplazar en una ruta determinada, y tome solo la ruta completa del archivo coincidente. (ese sería el $(grep 'string' 'pathname' -Rl).

  • (opcional) si desea hacer una copia de seguridad previa de esos archivos en el directorio centralizado, puede usar esto también: cp -iv $(grep 'string' 'pathname' -Rl) 'centralized-directory-pathname'

  • después de eso, puede editar / reemplazar a voluntad vimsiguiendo un esquema similar al proporcionado en el enlace proporcionado:

    • :bufdo %s#string#replacement#gc | update

2

Un poco vieja escuela, pero esto funcionó en OS X.

Hay algunos trucos:

• Solo editará archivos con extensión .sls bajo el directorio actual

.debe escaparse para garantizarsed que no los evalúa como "cualquier personaje"

,se usa como seddelimitador en lugar del habitual/

También tenga en cuenta que esto es para editar una plantilla Jinja para pasar un variableen la ruta de un import(pero esto está fuera de tema).

Primero, verifique que su comando sed haga lo que desea (esto solo imprimirá los cambios en stdout, no cambiará los archivos):

for file in $(find . -name *.sls -type f); do echo -e "\n$file: "; sed 's,foo\.bar,foo/bar/\"+baz+\"/,g' $file; done

Edite el comando sed según sea necesario, una vez que esté listo para realizar cambios:

for file in $(find . -name *.sls -type f); do echo -e "\n$file: "; sed -i '' 's,foo\.bar,foo/bar/\"+baz+\"/,g' $file; done

Tenga -i ''en cuenta que en el comando sed , no quería crear una copia de seguridad de los archivos originales (como se explica en Ediciones in situ con sed en OS X o en el comentario de Robert Lujo en esta página).

¡Felices amigos!


2

solo para evitar cambiar también

  • NearlysubdomainA.example.com
  • subdominioA.example.comp.other

pero aún

  • subdominioA.example.com.IsIt.good

(tal vez no sea bueno en la idea detrás del dominio raíz)

find /home/www/ -type f -exec sed -i 's/\bsubdomainA\.example\.com\b/\1subdomainB.example.com\2/g' {} \;

2

Solo uso tops:

find . -name '*.[c|cc|cp|cpp|m|mm|h]' -print0 |  xargs -0 tops -verbose  replace "verify_noerr(<b args>)" with "__Verify_noErr(<args>)" \
replace "check(<b args>)" with "__Check(<args>)" 

más uno para '' *. [c | cc | cp | cpp | m | mm | h] ''
FractalSpace

2

Aquí hay una versión que debería ser más general que la mayoría; no requiere find(usando en su dulugar), por ejemplo. Requiere xargs, que solo se encuentran en algunas versiones del Plan 9 (como 9front).

 du -a | awk -F' '  '{ print $2 }' | xargs sed -i -e 's/subdomainA\.example\.com/subdomainB.example.com/g'

Si desea agregar filtros como extensiones de archivo, use grep:

 du -a | grep "\.scala$" | awk -F' '  '{ print $2 }' | xargs sed -i -e 's/subdomainA\.example\.com/subdomainB.example.com/g'

1

Para Qshell (qsh) en IBMi, no bash como lo etiqueta OP

Limitaciones de los comandos qsh:

  • find no tiene la opción -print0
  • xargs no tiene la opción -0
  • sed no tiene la opción -i

Así, la solución en qsh:

    PATH='your/path/here'
    SEARCH=\'subdomainA.example.com\'
    REPLACE=\'subdomainB.example.com\'

    for file in $( find ${PATH} -P -type f ); do

            TEMP_FILE=${file}.${RANDOM}.temp_file

            if [ ! -e ${TEMP_FILE} ]; then
                    touch -C 819 ${TEMP_FILE}

                    sed -e 's/'$SEARCH'/'$REPLACE'/g' \
                    < ${file} > ${TEMP_FILE}

                    mv ${TEMP_FILE} ${file}
            fi
    done

Advertencias:

  • La solución excluye el manejo de errores
  • No Bash como etiquetado por OP

Esto tiene algunos problemas molestos con las citas y con las líneas de lectura for.
tripleee

1

Si desea utilizar esto sin destruir por completo su repositorio SVN, puede decirle a 'buscar' que ignore todos los archivos ocultos haciendo lo siguiente:

find . \( ! -regex '.*/\..*' \) -type f -print0 | xargs -0 sed -i 's/subdomainA.example.com/subdomainB.example.com/g'

Los paréntesis parecen ser superfluos. Esto anteriormente tenía un error de formato que lo dejaba inutilizable (la representación de Markdown consumiría algunos caracteres de la expresión regular).
tripleee

1

Usando la combinación de grepysed

for pp in $(grep -Rl looking_for_string)
do
    sed -i 's/looking_for_string/something_other/g' "${pp}"
done

@tripleee Modifiqué esto un poco. En este caso, la salida para el comando grep -Rl patterngeneró la lista de archivos donde está el patrón. Los archivos no se leen en forbucle.
Pawel

¿Eh? Aún tienes un forbucle; si algún nombre de archivo devuelto contiene espacios en blanco, no funcionará correctamente porque el shell tokeniza la forlista de argumentos. Pero luego usa la variable de nombre de archivo sin comillas dentro del bucle, por lo que se rompería allí si solucionara esto. Corregir estos errores restantes haría que los suyos fueran idénticos a la respuesta de @ MadMan2064.
tripleee

@tripleee sí, eso es cierto, me perdí esto.
Pawel

1

Para reemplazar todas las ocurrencias en un repositorio de git puede usar:

git ls-files -z | xargs -0 sed -i 's/subdomainA\.example\.com/subdomainB.example.com/g'

Ver Lista de archivos en el repositorio local de git? para otras opciones para enumerar todos los archivos en un repositorio. Las -zopciones le dicen a git que separe los nombres de los archivos con un byte cero, lo que asegura que xargs(con la opción -0) puede separar los nombres de los archivos, incluso si contienen espacios o no.


1
perl -p -i -e 's/oldthing/new_thingy/g' `grep -ril oldthing *`

1
No usa awk/ sed, pero perl es común (excepto sistemas integrados / con solo busybox).
pevik

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.