Cómo reemplazar una cadena en varios archivos en la línea de comando de Linux

461

Necesito reemplazar una cadena en muchos archivos en una carpeta, con solo sshacceso al servidor. ¿Cómo puedo hacer esto?

mridul4c
fuente

Respuestas:

602
cd /path/to/your/folder
sed -i 's/foo/bar/g' *

Las apariciones de "foo" serán reemplazadas por "bar".

En los sistemas BSD como macOS, debe proporcionar una extensión de respaldo como -i '.bak'"riesgo de corrupción o contenido parcial" según la página de manual.

cd /path/to/your/folder
sed -i '.bak' 's/foo/bar/g' *
kev
fuente
13
Esto no parece funcionar para mí si la cadena tiene espacios en blanco o caracteres especiales. ¿Alguna idea de por qué podría ser eso, o necesito escapar de alguna manera? ¡Gracias!
Matthew Herbst
13
Al menos para mi versión de sed, -irequiere una cadena después, que se agrega a los nombres de archivo antiguos. Entonces sed -i .bak 's/foo/bar/g' *.xxmueve todos los .xxarchivos al .xx.baknombre equivalente y luego genera los .xxarchivos con la sustitución foo → barra.
Anaphory
24
Si alguien quiere buscar más opciones, hay una respuesta en el intercambio de pila de Unix que cubre más casos de uso del sitio unix.stackexchange.com/a/112024/13488
Reddy
19
@MatthewHerbst para escapar de espacios, use \ like sed -i 's/foo\ with\ spaces/bar/g' *. Supongo que te has enterado después de tanto tiempo ... pero de esta manera queda para que otros encuentren el mismo problema.
manuelvigarcia
55
Para algo recursivo puedes probar lo siguiente. Tenga en cuenta que no funciona si la lista de archivos es enorme. sed -i -e 's/foo/bar/g' $(find /home/user/base/dir)
Scot
244

Similar a la respuesta de Kaspar pero con la bandera g para reemplazar todas las ocurrencias en una línea.

find ./ -type f -exec sed -i 's/string1/string2/g' {} \;

Para mayúsculas y minúsculas:

find ./ -type f -exec sed -i 's/string1/string2/gI' {} \;
Céline Aussourd
fuente
23
Si está en OSX y sus patrones pueden contener puntos y desea un reemplazo en el lugar (sin archivo de respaldo) debe usar LC_ALL=C find ./ -type f -exec sed -i '' -e 's/proc.priv/priv/g' {} \;(vea esta publicación y esta )
Jonathan H
2
Esto definitivamente funcionó para mí, ya que permite el filtrado con -name "* .js" '
Marcello de Sales
1
Una opción detallada sería genial, pero puede volver a hacer grep para ver si se realizaron los cambios. Nota: Para los comodines, intente '-name "* .php"' y grep es malo con la recursividad y los comodines, debe agregar --include=*.whatevercon -r
PJ Brunet
12
No hagas esto desde la raíz de un repositorio Git desprotegido. Puede dañar accidentalmente su base de datos en .git/. Asegúrese de cdbajar a un nivel inferior.
erikprice
1
Acabo de hacer esto en mi repositorio git y ahora git statusdevuelve: error: bad index file sha1 signature.... fatal: index file corrupt. ¿Lo que da?
Jin
165

La respuesta de @ kev es buena, pero solo afecta a los archivos en el directorio inmediato. El siguiente ejemplo utiliza grep para buscar archivos de forma recursiva. A mí me funciona siempre.

grep -rli 'old-word' * | xargs -i@ sed -i 's/old-word/new-word/g' @

Desglose de comandos

grep -r : --recursivo , lee recursivamente todos los archivos de cada directorio.
grep -l : --print-with-coincide , imprime el nombre de cada archivo que tiene una coincidencia, en lugar de imprimir líneas coincidentes.
grep -i : --ignore-case .

xargs : transforma el STDIN en argumentos, sigue esta respuesta .
El comando xargs -i @ ~ contiene @ ~ : un marcador de posición para el argumento que se utilizará en una posición específica en el comando ~ , el signo @ es un marcador de posición que podría reemplazarse por cualquier cadena.

sed -i : edita archivos en su lugar, sin copias de seguridad.
sed s / regexp / replace / : sustituye la expresión regular de coincidencia de cadenas con reemplazo .
sed s / regexp / replace / g : global , realice la sustitución de cada coincidencia en lugar de solo la primera coincidencia.

pymarco
fuente
13
Esto no funcionó para mí, pero sí:grep --include={*.php,*.html,*.js} -rnl './' -e "old-word" | xargs -i@ sed -i 's/old-word/new-word/g' @
Dennis
77
@pymarco ¿Puede explicar este comando? No sé por qué tuvo que usar xargs en lugar de simplemente usar sed después |, también, ¿ -ipor qué en el comando xargs? Leí en el manual que está en desuso y debería usar-I en lugar. ¿Y se @usa como delimitador para el principio y el final del patrón?
Edson Horacio Junior
2
Sin embargo, la pregunta es específicamente para Linux.
pymarco
66
En osx esto está bien:grep -rli 'old-word' * | xargs -I@ sed -i '' 's/2.0.0/latest/g' @
Philippe Sultan
1
Sería bueno si desglosas algunas de las opciones ( rli) y el @letrero, por ejemplo
Roymunson
37

Esto funcionó para mí:

find ./ -type f -exec sed -i 's/string1/string2/' {} \;

Howerver, no hizo esto: sed -i 's/string1/string2/g' *. Tal vez "foo" no estaba destinado a ser string1 y "bar" no a string2.

Kaspar L. Palgi
fuente
1
Es porque sed trata el comodín * de manera diferente. [abc] * significa un número arbitrario de caracteres del conjunto {a, b, c}. [a-z0-9] * funciona de manera similar al comodín *.
thepiercingarrow
8
Sobre el uso de OSX:find ./ -type f -exec sed -i '' -e 's/string1/string2/' {} \;
Shaheen Ghiassy
32

Hay algunas respuestas estándar a esto ya enumeradas. En general, puede usar find para enumerar recursivamente los archivos y luego realizar las operaciones con sed o perl .

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

rpl -R foo bar .

Probablemente necesites instalarlo (apt-get install rpl o 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 búsqueda y reemplazo, la herramienta más general y poderosa que conozco es la respuesta , un pequeño script de Python que escribí hace un tiempo. tareas de cambio de nombre y refactorización más espinosas. Las razones por las que puede preferirlo son:

  • Admite el cambio de nombre de los archivos, así como la búsqueda y reemplazo del contenido del archivo.
  • Vea los cambios antes de comprometerse a realizar la búsqueda y reemplazar.
  • Admite expresiones regulares con sustitución hacia atrás, palabras completas, mayúsculas y minúsculas y modos de preservación de mayúsculas y minúsculas (reemplazar foo -> bar, Foo -> Bar, FOO -> BAR).
  • Funciona con reemplazos múltiples, incluidos swaps (foo -> bar y bar -> foo) o conjuntos de reemplazos no únicos (foo -> bar, f -> x).

Para usarlo, pip install repren. Consulte el archivo README para ver ejemplos.

jlevy
fuente
1
¡Guau, la respuesta es excelente! Solo lo usé para cambiar parte de una palabra dentro de los nombres de clase, métodos y variables al cambiar el nombre de los archivos para que coincidan con más de 1,000 encabezados C ++ y archivos fuente y funcionó perfectamente la primera vez con un comando. ¡Gracias!
Bob Kocisko el
29

Para reemplazar una cadena en varios archivos puede usar:

grep -rl string1 somedir/ | xargs sed -i 's/string1/string2/g'

P.ej

grep -rl 'windows' ./ | xargs sed -i 's/windows/linux/g'

Blog de origen

Djacomo
fuente
20

Para reemplazar una ruta dentro de los archivos (evitando caracteres de escape) puede usar el siguiente comando:

sed -i 's@old_path@new_path@g'

El signo @ significa que todos los caracteres especiales deben ignorarse en la siguiente cadena.

Ilya Suzdalnitski
fuente
2
Exactamente lo que estaba buscando en todas las otras respuestas. A saber, cómo tratar con caracteres especiales, como cuando se cambia una cadena que es una ruta. Gracias. Parecía un gran descuido en las otras respuestas.
inspirado
19

En caso de que su cadena tenga una barra diagonal (/), puede cambiar el delimitador a '+'.

find . -type f -exec sed -i 's+http://example.com+https://example.com+g' {} +

Este comando se ejecutaría recursivamente en el directorio actual.

gopiariv
fuente
¡Gracias! Ayudado a cambiar una carga de referencias de ../domain.comadomain.com
Jack Rogers
12

Las apariciones de la primera línea de "foo" serán reemplazadas por "bar". Y puede usar la segunda línea para verificar.

grep -rl 'foo' . | xargs sed -i 's/foo/bar/g'
grep 'foo' -r * | awk -F: {'print $1'} | sort -n | uniq -c
jiasir
fuente
66
¿Por qué estás enlazando a tu blog? Contiene exactamente el mismo texto que su respuesta.
DavidPostill
1
grep -rl 'foo' . | xargs sed -i 's/foo/bar/g'
Me
10

Si tiene una lista de archivos que puede usar

replace "old_string" "new_string" -- file_name1 file_name2 file_name3

Si tiene todos los archivos que puede usar

replace "old_string" "new_string" -- *

Si tiene una lista de archivos con extensión, puede usar

replace "old_string" "new_string" -- *.extension
Pawel Dubiel
fuente
2
en realidad, "--file" debería ser simplemente "-", al menos en mi versión
simpleuser
44
Esta utilidad se distribuye dentro de los paquetes MySQL.
Dmitry Ginzburg
2
Aunque me gustaría apreciar la solución, que funciona sin citar la línea regular como expresión regular.
Dmitry Ginzburg
1
Esto funciona bien, y si desea reemplazar todas las cadenas en varios archivos, que terminan en, por ejemplo, ".txt", puede hacerloreplace "old_string" "new_string" -- *.txt
tsveti_iko
1
Es mejor agregar de dónde obtener esta replaceutilidad. Sin esta información, esta respuesta está incompleta.
Sitesh
8

"También podrías usar find y sed, pero creo que esta pequeña línea de perl funciona muy bien.

perl -pi -w -e 's/search/replace/g;' *.php
  • -e significa ejecutar la siguiente línea de código.
  • -i significa editar en el lugar
  • -w escribir advertencias
  • -p loop

"(Extraído de http://www.liamdelahunty.com/tips/linux_search_and_replace_multiple_files.php )

Mis mejores resultados provienen del uso de perl y grep (para asegurar que el archivo tenga la expresión de búsqueda)

perl -pi -w -e 's/search/replace/g;' $( grep -rl 'search' )
Alejandro Salamanca Mazuelo
fuente
8

Dado que desea buscar la cadena searchy reemplazarla con replacevarios archivos, esta es mi fórmula de una línea probada en batalla :

grep -RiIl 'search' | xargs sed -i 's/search/replace/g'

Explicación rápida de grep:

  • -R - búsqueda recursiva
  • -i - insensible a mayúsculas y minúsculas
  • -I - saltar archivos binarios (quieres texto, ¿verdad?)
  • -l- Imprima una lista simple como salida. Necesario para los otros comandos

La salida grep se canaliza a sed (a través de xargs) que se usa para reemplazar el texto. La -ibandera alterará el archivo directamente. Elimínelo para una especie de modo "funcionamiento en seco".

Ignorante
fuente
4

Hice mi propia solución antes de encontrar esta pregunta (y respuestas). Busqué diferentes combinaciones de "reemplazar" "varios" y "xml", porque esa era mi aplicación, pero no encontré esta en particular.

Mi problema: tenía archivos xml de primavera con datos para casos de prueba, que contenían objetos complejos. Un refactor en el código fuente de Java cambió muchas clases y no se aplicó a los archivos de datos XML. Para guardar los datos de los casos de prueba, necesitaba cambiar todos los nombres de clase en todos los archivos xml, distribuidos en varios directorios. Todo mientras guardo copias de seguridad de los archivos xml originales (aunque esto no era obligatorio, ya que el control de versiones me salvaría aquí).

Estaba buscando alguna combinación de find+sed , porque funcionó para mí en otras situaciones, pero no con varios reemplazos a la vez.

Luego encontré preguntar la respuesta de ubuntu y me ayudó a construir mi línea de comando:

find -name "*.xml" -exec sed -s --in-place=.bak -e 's/firstWord/newFirstWord/g;s/secondWord/newSecondWord/g;s/thirdWord/newThirdWord/g' {} \;

Y funcionó perfectamente (bueno, mi caso tenía seis reemplazos diferentes). Pero tenga en cuenta que tocará todos los archivos * .xml en el directorio actual. Por eso, y si usted es responsable de un sistema de control de versiones, es posible que desee filtrar primero y solo pasar a sedaquellos que realmente tienen las cadenas que desea; me gusta:

find -name "*.xml" -exec grep -e "firstWord" -e "secondWord" -e "thirdWord" {} \; -exec sed -s --in-place=.bak -e 's/firstWord/newFirstWord/g;s/secondWord/newSecondWord/g;s/thirdWord/newThirdWord/g' {} \;
manuelvigarcia
fuente
y para Windows, descubrí que hay una manera de encontrar la cadena, aún no comprobé cómo reemplazarla, en un solo comando: findstr /spin /c:"quéquieresbuscar" *.xml será útil.
manuelvigarcia
3
grep --include={*.php,*.html} -rnl './' -e "old" | xargs -i@ sed -i 's/old/new/g' @
Dennis
fuente
3

Realmente cojo, pero no pude hacer que ninguno de los comandos sed funcionara correctamente en OSX, así que hice esta tontería:

:%s/foo/bar/g
:wn

^ - copie estas tres líneas en mi portapapeles (sí, incluya la nueva línea final), luego:

vi *

y mantenga presionado command-v hasta que diga que no quedan archivos.

Tonto ... hacky ... efectivo ...

Justin
fuente
3

En una MacBook Pro, utilicé lo siguiente (inspirado en https://stackoverflow.com/a/19457213/6169225 ):

sed -i '' -e 's/<STR_TO_REPLACE>/<REPLACEMENT_STR>/g' *

-i '' se asegurará de que no realice copias de seguridad.

-e para expresiones regulares modernas.

Marquistador
fuente
3
-esolo le dice a sed que el siguiente token es un comando. Para expresiones regulares extendidas, use -Een su lugar.
Benjamin W.
2

El editor de flujo modifica múltiples archivos "in situ" cuando se invoca con el -iinterruptor, lo que toma un archivo de respaldo que termina como argumento. Entonces

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

reemplaza foocon baren todos los archivos en esta carpeta, pero no desciende en subcarpetas. Sin embargo, esto generará un nuevo .bakarchivo para cada archivo en su directorio. Para hacer esto de forma recursiva para todos los archivos en este directorio y todos sus subdirectorios, necesita un ayudante, como find, para atravesar el árbol de directorios.

find ./ -print0 | xargs -0 sed -i.bak 's/foo/bar/g' *

findle permite más restricciones sobre qué archivos modificar, especificando argumentos adicionales como find ./ -name '*.php' -or -name '*.html' -print0, si es necesario.


Nota: GNU sedno requiere un final de archivo, sed -i 's/foo/bar/g' *también funcionará; FreeBSD sedexige una extensión, pero permite un espacio intermedio, por lo que sed -i .bak s/foo/bar/g *funciona.

Anaphory
fuente
2

script para comando multiedit

multiedit [-n PATTERN] OLDSTRING NEWSTRING

A partir de la respuesta de Kaspar, hice un script bash para aceptar argumentos de línea de comando y, opcionalmente, limitar los nombres de archivo que coinciden con un patrón. Ahorre en su $ PATH y haga ejecutable, luego simplemente use el comando anterior.

Aquí está el guión:

#!/bin/bash
_help="\n
Replace OLDSTRING with NEWSTRING recursively starting from current directory\n
multiedit [-n PATTERN] OLDSTRING NEWSTRING\n

[-n PATTERN] option limits to filenames matching PATTERN\n
Note: backslash escape special characters\n
Note: enclose STRINGS with spaces in double quotes\n
Example to limit the edit to python files:\n
multiedit -n \*.py \"OLD STRING\" NEWSTRING\n"

# ensure correct number of arguments, otherwise display help...
if [ $# -lt 2 ] || [ $# -gt 4 ]; then echo -e $_help ; exit ; fi
if [ $1 == "-n" ]; then  # if -n option is given:
        # replace OLDSTRING with NEWSTRING recursively in files matching PATTERN
        find ./ -type f -name "$2" -exec sed -i "s/$3/$4/g" {} \;
else
        # replace OLDSTRING with NEWSTRING recursively in all files
        find ./ -type f -exec sed -i "s/$1/$2/" {} \;
fi
Bbw antes de windows
fuente
1

Si el archivo contiene barras inclinadas invertidas (rutas generalmente), puede intentar algo como esto:

sed -i -- 's,<path1>,<path2>,g' *

ex:

sed -i -- 's,/foo/bar,/new/foo/bar,g' *.sh (in all shell scripts available)
Shaik Amanulla
fuente
1

Para mantener mi nodo personal en inglés, escribí un script de utilidad que ayuda a reemplazar varios pares de cadenas viejas / nuevas, para todos los archivos en un directorio de forma recursiva.

Los múltiples pares de cadenas antiguas / nuevas se administran en un mapa hash.

El directorio se puede establecer a través de la línea de comando o la variable de entorno, el mapa está codificado en el script, pero puede modificar el código para cargarlo desde un archivo, si es necesario.

Requiere bash 4.2, debido a alguna nueva característica.

en_standardize.sh:

#! /bin/bash
# (need bash 4.2+,)
# 
# Standardize phonetic symbol of English.
# 
# format:
#   en_standardize.sh [<dir>]
# 
# params:
# * dir
#   target dir, optional,
#   if not specified then use environment variable "$node_dir_en",
#   if both not provided, then will not execute,
# * 
# 

paramCount=$#

# figure target dir,
if [ $paramCount -ge 1 ]; then # dir specified
    echo -e "dir specified (in command):\n\t$1\n"
    targetDir=$1
elif [[ -v node_dir_en ]]; then # environable set,
    echo -e "dir specified (in environment vairable):\n\t$node_dir_en\n"
    targetDir=$node_dir_en
else # environable not set,
    echo "dir not specified, won't execute"
    exit
fi

# check whether dir exists,
if [ -d $targetDir ]; then
    cd $targetDir
else
    echo -e "invalid dir location:\n\t$targetDir\n"
    exit
fi

# initial map,
declare -A itemMap
itemMap=( ["ɪ"]="i" ["ː"]=":" ["ɜ"]="ə" ["ɒ"]="ɔ" ["ʊ"]="u" ["ɛ"]="e")

# print item maps,
echo 'maps:'
for key in "${!itemMap[@]}"; do
    echo -e "\t$key\t->\t${itemMap[$key]}"
done
echo -e '\n'

# do replace,
for key in "${!itemMap[@]}"; do
    grep -rli "$key" * | xargs -i@ sed -i "s/$key/${itemMap[$key]}/g" @
done

echo -e "\nDone."
exit
Eric Wang
fuente
1

Usar el comando ack sería mucho más rápido así:

ack '25 Essex' -l | xargs sed -i 's/The\ fox \jump/abc 321/g'

Además, si tiene un espacio en blanco en el resultado de la búsqueda. Necesitas escapar de eso.

Patoshi パ ト シ
fuente
0

Estoy dando un ejemplo para corregir un error común de shebang en las fuentes de Python.

Puedes probar el enfoque grep / sed. Aquí hay uno que funciona con GNU sed y no romperá un repositorio git:

$ grep -rli --exclude '*.git*' '#!/usr/bin/python' . | xargs -I {} \
gsed -i '' -e 's/#!\/usr\/bin\/python/#!\/usr\/bin\/env python/' {}

O puedes usar greptile :)

$ greptile -x .py -l -i -g '#!/usr/bin/env python' -r '#!/usr/bin/python' .

Acabo de probar el primer script, y el segundo debería funcionar también. Tenga cuidado con los caracteres de escape, creo que debería ser más fácil usar greptile en la mayoría de los casos. Por supuesto, puede hacer muchas cosas interesantes con sed, y para eso puede ser preferible dominar su uso con xargs.

exa
fuente
0

Encontré este de otra publicación (no recuerdo cuál) y, aunque no es el más elegante, es simple y, como usuario novato de Linux, no me ha causado problemas

for i in *old_str* ; do mv -v "$i" "${i/\old_str/new_str}" ; done

si tiene espacios u otros caracteres especiales, use un \

for i in *old_str\ * ; do mv -v "$i" "${i/\old_str\ /new_str}" ; done

para cadenas en subdirectorios use **

for i in *\*old_str\ * ; do mv -v "$i" "${i/\old_str\ /new_str}" ; done
Kyle Turck
fuente
0

El siguiente comando se puede usar para buscar primero los archivos y reemplazar los archivos:

find . | xargs grep 'search string' | sed 's/search string/new string/g'

Por ejemplo

find . | xargs grep abc | sed 's/abc/xyz/g'
Amit
fuente
0

Hay una manera más fácil mediante el uso de un archivo de script simple:

   # sudo chmod +x /bin/replace_string_files_present_dir

abra el archivo en gedit o un editor de su elección, yo uso gedit aquí.

   # sudo gedit /bin/replace_string_files_present_dir

Luego, en el editor, pegue lo siguiente en el archivo

   #!/bin/bash
   replace "oldstring" "newstring" -- *
   replace "oldstring1" "newstring2" -- *
   #add as many lines of replace as there are your strings to be replaced for 
   #example here i have two sets of strings to replace which are oldstring and 
   #oldstring1 so I use two replace lines.

Guarde el archivo, cierre gedit , luego salga de su terminal o simplemente ciérrelo y luego inícielo para poder cargar el nuevo script que agregó.

Navegue hasta el directorio donde tiene varios archivos que desea editar. Entonces corre:

  #replace_string_files_present_dir

Presione Intro y esto reemplazará automáticamente oldstring y oldstring1 en todos los archivos que los contienen con la cadena de noticias correcta y la cadena de noticias1 respectivamente.

Omitirá todos los directorios y archivos que no contengan las cadenas antiguas.

Esto podría ayudar a eliminar el tedioso trabajo de escribir en caso de que tenga varios directorios con archivos que necesitan reemplazo de cadena. Todo lo que tiene que hacer es navegar a cada uno de esos directorios, luego ejecutar:

#replace_string_files_present_dir

Todo lo que tiene que hacer es asegurarse de haber incluido o agregado todas las cadenas de reemplazo como le mostré anteriormente:

replace "oldstring" "newstring" -- *

al final del archivo / bin / replace_string_files_present_dir .

Para agregar una nueva cadena de reemplazo, simplemente abra el script que creamos escribiendo lo siguiente en el terminal:

sudo gedit /bin/replace_string_files_present_dir

No se preocupe por la cantidad de cadenas de reemplazo que agregue, no tendrán efecto si no se encuentra la cadena antigua .

briancollins081
fuente
Por lo general, cuando las personas preguntan "cómo $ {lo que sea}" en bash, piden una versión compacta para incluir en un script o instrucción de trabajos de CI (digamos .gitlab-ci.ymlao a travis.yml). Cada vuelta que consiste en escribir un guión para ejecutarlo más tarde es un antipatrón, ya que luego necesitará guiar la creación del guión (y la mayoría de las veces me da problemas)
zar3bski
-4

$ reemplaza "From" "To" - `find / path / to / folder -type f`

(reemplazar - una utilidad de reemplazo de cadenas del sistema de base de datos MySQL)

bashconsole
fuente