Buscar un texto en todos los archivos de un proyecto y cambiarlo por otro es una línea de Bash: grep encuentra y sed cambia. Aquí tienes cómo hacerlo sin romper nada: probar antes de aplicar, guardar una copia, tratar el patrón como texto literal, sobrevivir a los nombres con espacios y a los caracteres especiales, y la diferencia de sed -i en macOS.
También te puede interesar
Supongamos que una web se muda de http://www.example.com a https://www.example.com y hay que cambiar la dirección en todos los archivos de configuración. Esta es la carpeta:
terminal$ find . -type f | sort ./config/app.conf ./config/base.conf ./notas.txt ./web/index.html
Buscar: grep -r
terminal$ grep -rn 'http://www.example.com' . | sort ./config/app.conf:2:url=http://www.example.com/app ./config/base.conf:2:url_respaldo=http://www.example.com/respaldo ./web/index.html:1:<a href="http://www.example.com/">Inicio</a> $ grep -rl 'http://www.example.com' . | sort ./config/app.conf ./config/base.conf ./web/index.html $ grep -rc 'example' --include='*.conf' . | sort ./config/app.conf:1 ./config/base.conf:2
-r busca en la carpeta y todas sus subcarpetas, -n añade el número de línea, -l lista solo los archivos que contienen el texto y -c cuenta las coincidencias de cada archivo. --include limita la búsqueda a los nombres que encajan con un patrón (y --exclude hace lo contrario). El | sort solo está para que el orden sea siempre el mismo.
Cuidado: el patrón es una expresión regular
Para grep, el punto significa «cualquier carácter», y además busca el texto en cualquier parte de la línea, también dentro de palabras más largas:
terminal$ grep -n 'example.com' notas.txt 1:La web vieja era example.company 2:El correo va a example.com $ grep -nw 'example.com' notas.txt 2:El correo va a example.com $ grep -nF 'example.com' notas.txt 1:La web vieja era example.company 2:El correo va a example.com $ grep -nwF 'example.com' notas.txt 2:El correo va a example.com
-w exige que la coincidencia sea una palabra completa, y -F trata el patrón como texto literal, sin expresiones regulares. Cuando buscas una dirección, una ruta o cualquier cosa con puntos, -F es lo más seguro.
Reemplazar: probar con sed y luego sed -i
sed 's/viejo/nuevo/g' cambia todas las apariciones (la g del final; sin ella, solo la primera de cada línea). Sin -i, sed imprime el resultado y no toca el archivo, que es justo lo que quieres para probar:
terminal$ sed 's|http://www\.example\.com|https://www.example.com|g' config/app.conf nombre=Tienda de prueba url=https://www.example.com/app registro=/var/log/app.log $ cat config/app.conf nombre=Tienda de prueba url=http://www.example.com/app registro=/var/log/app.log
Como el texto tiene barras, se usa otro separador: s|…|…| funciona igual que s/…/…/ y evita tener que escapar cada /. Los puntos del patrón van escapados, \., para que signifiquen un punto de verdad. Cuando el resultado es el que quieres, -i modifica el archivo, y -i.bak guarda antes una copia con esa extensión:
terminal$ sed -i.bak 's|http://www\.example\.com|https://www.example.com|g' config/app.conf $ diff config/app.conf.bak config/app.conf 2c2 < url=http://www.example.com/app --- > url=https://www.example.com/app
En todos los archivos a la vez
Se combinan las dos cosas: grep -rl elige los archivos que contienen el texto y xargs se los pasa a sed -i. Con -Z en grep y -0 en xargs, los nombres viajan separados por un carácter nulo, así que funcionan aunque tengan espacios o saltos de línea:
terminal$ grep -rlZF 'http://www.example.com' . | xargs -0 -r sed -i.bak 's|http://www\.example\.com|https://www.example.com|g' $ grep -rn 'example.com' --exclude='*.bak' . | sort ./config/app.conf:2:url=https://www.example.com/app ./config/base.conf:1:servidor=db.example.com ./config/base.conf:2:url_respaldo=https://www.example.com/respaldo ./notas.txt:1:La web vieja era example.company ./notas.txt:2:El correo va a example.com ./web/index.html:1:<a href="https://www.example.com/">Inicio</a> ./web/index.html:2:<p>Escríbenos a hola@example.com</p>
La -r de xargs (de GNU) evita que ejecute sed sin archivos si grep no encontró nada.
Los caracteres especiales en el reemplazo
En la parte del reemplazo, & significa «el texto que coincidió», y eso da sorpresas:
terminal$ echo "Llama a la compañía AT" | sed 's/AT/AT&T/' Llama a la compañía ATATT $ echo "Llama a la compañía AT" | sed 's/AT/AT\&T/' Llama a la compañía AT&T
El problema crece cuando el texto viejo o el nuevo vienen de variables, porque pueden contener /, &, puntos o corchetes. Este script los escapa antes de construir la orden de sed, busca con grep -F (texto literal) y deja una copia .bak de cada archivo cambiado:
#!/usr/bin/env bash
# Uso: reemplazar.sh TEXTO_VIEJO TEXTO_NUEVO
# Cambia el texto en todos los archivos de la carpeta actual y sus subcarpetas, guardando una copia .bak de cada uno.
if (( $# != 2 )); then
echo "Uso: ${0##*/} TEXTO_VIEJO TEXTO_NUEVO" >&2
exit 2
fi
# Escapa lo que sed interpretaría: en el patrón, los caracteres especiales de las expresiones regulares básicas y la
# barra; en el reemplazo, la barra, la barra invertida y el &.
escapar_patron() { printf '%s' "$1" | sed -e 's/[]\/$*.^[]/\\&/g'; }
escapar_reemplazo() { printf '%s' "$1" | sed -e 's/[\/&]/\\&/g'; }
viejo=$(escapar_patron "$1")
nuevo=$(escapar_reemplazo "$2")
grep -rlZF --exclude='*.bak' -- "$1" . | xargs -0 -r sed -i.bak -e "s/$viejo/$nuevo/g"
echo "Archivos cambiados:"
find . -name '*.bak' -print | sed 's/\.bak$//' | sortterminal$ bash reemplazar.sh 'http://www.example.com' 'https://www.example.com' Archivos cambiados: ./config/app.conf ./config/base.conf ./web/index.html $ grep -rn 'example.com' --exclude='*.bak' . | sort ./config/app.conf:2:url=https://www.example.com/app ./config/base.conf:1:servidor=db.example.com ./config/base.conf:2:url_respaldo=https://www.example.com/respaldo ./notas.txt:1:La web vieja era example.company ./notas.txt:2:El correo va a example.com ./web/index.html:1:<a href="https://www.example.com/">Inicio</a> ./web/index.html:2:<p>Escríbenos a hola@example.com</p>
sed -i en macOS
El sed de macOS es el de BSD, y su -i exige siempre una extensión para la copia: sed -i '' 's/a/b/' archivo para no hacerla. Al revés, sed -i '' falla en Linux. La forma que funciona en los dos es la de esta página, sed -i.bak, pegada y con copia. Tampoco existe allí el modificador I para no distinguir mayúsculas, ni xargs -r. Si trabajas a menudo en macOS, brew install gnu-sed instala el de GNU como gsed.
- ParaOrden
- Ver dónde aparece
grep -rn 'texto' . - Solo los archivos
grep -rl 'texto' . - Texto literal, sin expresiones regulares
grep -F - Palabra completa
grep -w - Probar el cambio
sed 's/viejo/nuevo/g' archivo - Aplicarlo con copia
sed -i.bak 's/viejo/nuevo/g' archivo - En muchos archivos
grep -rlZ 'viejo' . | xargs -0 -r sed -i.bak 's/viejo/nuevo/g'
.bak, con una copia de la carpeta o, mejor todavía, con el proyecto en Git, donde git diff te enseña exactamente qué cambió y git checkout . lo deshace.Todos los scripts de esta página se ejecutaron con Bash 5.3 en Ubuntu 26.04, en un contenedor limpio sin acceso a la red, y pasaron ShellCheck 0.11 sin avisos antes de publicarla; las salidas están copiadas de esa ejecución.