Reemplazar cadenas en archivos según ciertos criterios de búsqueda es una tarea muy común. ¿Cómo puedo?
- reemplazar cadena
foo
conbar
en todos los archivos del directorio actual? - hacer lo mismo recursivamente para subdirectorios?
- ¿reemplazar solo si el nombre del archivo coincide con otra cadena?
- ¿reemplazar solo si la cadena se encuentra en un contexto determinado?
- ¿reemplazar si la cadena está en un determinado número de línea?
- reemplace varias cadenas con el mismo reemplazo
- reemplazar múltiples cadenas con diferentes reemplazos
Respuesta aceptada:
1. Reemplazando todas las apariciones de una cadena con otra en todos los archivos en el directorio actual:
Estos son para casos en los que sabe que el directorio contiene solo archivos normales y que desea procesar todos los archivos no ocultos. Si ese no es el caso, use los enfoques en 2.
Todo sed
las soluciones en esta respuesta asumen GNU sed
. Si usa FreeBSD o macOS, reemplace -i
con -i ''
. También tenga en cuenta que el uso de -i
cambiar con cualquier versión de sed
tiene ciertas implicaciones para la seguridad del sistema de archivos y no es aconsejable en ningún script que planee distribuir de alguna manera.
-
Archivos no recursivos, solo en este directorio:
sed -i -- 's/foo/bar/g' * perl -i -pe 's/foo/bar/g' ./*
(el perl
uno fallará para los nombres de archivo que terminan en |
o espacio)).
-
Archivos regulares recursivos (incluidos los ocultos ) en este y todos los subdirectorios
find . -type f -exec sed -i 's/foo/bar/g' {} +
Si está utilizando zsh:
sed -i -- 's/foo/bar/g' **/*(D.)
(puede fallar si la lista es demasiado grande, consulte
zargs
para evitarlo).Bash no puede buscar archivos regulares directamente, se necesita un bucle (las llaves evitan configurar las opciones globalmente):
( shopt -s globstar dotglob; for file in **; do if [[ -f $file ]] && [[ -w $file ]]; then sed -i -- 's/foo/bar/g' "$file" fi done )
Los archivos se seleccionan cuando son archivos reales (-f) y se pueden escribir (-w).
2. Reemplace solo si el nombre del archivo coincide con otra cadena/tiene una extensión específica/es de cierto tipo, etc.:
-
Archivos no recursivos, solo en este directorio:
sed -i -- 's/foo/bar/g' *baz* ## all files whose name contains baz sed -i -- 's/foo/bar/g' *.baz ## files ending in .baz
-
Archivos regulares recursivos en este y todos los subdirectorios
find . -type f -name "*baz*" -exec sed -i 's/foo/bar/g' {} +
Si está utilizando bash (las llaves evitan configurar las opciones globalmente):
( shopt -s globstar dotglob sed -i -- 's/foo/bar/g' **baz* sed -i -- 's/foo/bar/g' **.baz )
Si está utilizando zsh:
sed -i -- 's/foo/bar/g' **/*baz*(D.) sed -i -- 's/foo/bar/g' **/*.baz(D.)
El --
sirve para decirle a sed
que no se darán más banderas en la línea de comando. Esto es útil para proteger contra los nombres de archivo que comienzan con -
.
-
Si un archivo es de cierto tipo, por ejemplo, ejecutable (ver
man find
para más opciones):find . -type f -executable -exec sed -i 's/foo/bar/g' {} +
zsh
:
sed -i -- 's/foo/bar/g' **/*(D*)
3. Reemplace solo si la cadena se encuentra en un contexto determinado
-
Reemplazar
foo
conbar
solo si hay unbaz
más adelante en la misma línea:sed -i 's/foo(.*baz)/bar1/' file
En sed
, usando ( )
guarda lo que está entre paréntesis y luego puede acceder a él con 1
. Hay muchas variaciones de este tema, para obtener más información sobre estas expresiones regulares, consulta aquí.
-
Reemplazar
foo
conbar
solo sifoo
se encuentra en la columna 3d (campo) del archivo de entrada (asumiendo campos separados por espacios en blanco):gawk -i inplace '{gsub(/foo/,"baz",$3); print}' file
(necesita gawk
4.1.0 o posterior).
-
Para un campo diferente, simplemente use
$N
dondeN
es el número del campo de interés. Para un separador de campo diferente (:
en este ejemplo) use:gawk -i inplace -F':' '{gsub(/foo/,"baz",$3);print}' file
Otra solución usando perl
:
perl -i -ane '$F[2]=~s/foo/baz/g; $" = " "; print "@Fn"' foo
NOTA:tanto el awk
y perl
las soluciones afectarán el espacio en el archivo (elimine los espacios en blanco iniciales y finales, y convierta las secuencias de espacios en blanco en un carácter de espacio en aquellas líneas que coincidan). Para un campo diferente, use $F[N-1]
donde N
es el número de campo que desea y para usar un separador de campo diferente (el $"=":"
establece el separador de campo de salida en :
):
perl -i -F':' -ane '$F[2]=~s/foo/baz/g; $"=":";print "@F"' foo
-
Reemplazar
foo
conbar
sólo en la 4ª línea:sed -i '4s/foo/bar/g' file gawk -i inplace 'NR==4{gsub(/foo/,"baz")};1' file perl -i -pe 's/foo/bar/g if $.==4' file
4. Múltiples operaciones de reemplazo:reemplazar con diferentes cadenas
-
Puedes combinar
sed
comandos:sed -i 's/foo/bar/g; s/baz/zab/g; s/Alice/Joan/g' file
Tenga en cuenta que el orden importa (sed 's/foo/bar/g; s/bar/baz/g'
sustituirá foo
con baz
).
-
o comandos Perl
perl -i -pe 's/foo/bar/g; s/baz/zab/g; s/Alice/Joan/g' file
-
Si tiene una gran cantidad de patrones, es más fácil guardar sus patrones y sus reemplazos en un
sed
archivo de secuencia de comandos:#! /usr/bin/sed -f s/foo/bar/g s/baz/zab/g
-
O bien, si tiene demasiados pares de patrones para que lo anterior sea factible, puede leer los pares de patrones de un archivo (dos patrones separados por espacios, $patrón y $reemplazo, por línea):
while read -r pattern replacement; do sed -i "s/$pattern/$replacement/" file done < patterns.txt
-
Eso será bastante lento para largas listas de patrones y grandes archivos de datos, por lo que es posible que desee leer los patrones y crear un
sed
guión de ellos en su lugar. Lo siguiente asume un <espacio> el delimitador separa una lista de COINCIDIR<espacio>REEMPLAZAR pares que ocurren uno por línea en el archivopatterns.txt
:sed 's| *([^ ]*) *([^ ]*).*|s/1/2/g|' <patterns.txt | sed -f- ./editfile >outfile
El formato anterior es en gran medida arbitrario y, por ejemplo, no permite un <espacio> en cualquiera de PARTIDO o REEMPLAZAR . Sin embargo, el método es muy general:básicamente, si puede crear un flujo de salida que se parece a un sed
secuencia de comandos, entonces puede generar esa transmisión como un sed
secuencia de comandos especificando sed
archivo de script como -
estándar.
-
Puede combinar y concatenar varios scripts de manera similar:
SOME_PIPELINE | sed -e'#some expression script' -f./script_file -f- -e'#more inline expressions' ./actual_edit_file >./outfile
Un POSIX sed
concatenará todos los scripts en uno en el orden en que aparecen en la línea de comandos. Ninguno de estos necesita terminar en un n
nueva línea.
-
grep
puede funcionar de la misma manera:sed -e'#generate a pattern list' <in | grep -f- ./grepped_file
-
Cuando se trabaja con cadenas fijas como patrones, es una buena práctica escapar de los metacaracteres de expresiones regulares. . Puedes hacer esto con bastante facilidad:
sed 's/[]$&^*./[]/\&/g s| *([^ ]*) *([^ ]*).*|s/1/2/g| ' <patterns.txt | sed -f- ./editfile >outfile
5. Múltiples operaciones de reemplazo:reemplaza múltiples patrones con la misma cadena
-
Reemplace cualquiera de
foo
,bar
obaz
confoobar
sed -Ei 's/foo|bar|baz/foobar/g' file
-
o
perl -i -pe 's/foo|bar|baz/foobar/g' file