Buscar y reemplazar dentro de un archivo de texto desde un comando Bash

561

¿Cuál es la forma más sencilla de buscar y reemplazar una cadena de entrada determinada, por ejemplo abc, y reemplazarla por otra cadena, por ejemplo, XYZen el archivo /tmp/file.txt?

Estoy escribiendo una aplicación y usando IronPython para ejecutar comandos a través de SSH, pero no conozco a Unix tan bien y no sé qué buscar.

He oído que Bash, además de ser una interfaz de línea de comandos, puede ser un lenguaje de script muy poderoso. Entonces, si esto es cierto, supongo que puede realizar acciones como estas.

¿Puedo hacerlo con bash, y cuál es el script más simple (una línea) para lograr mi objetivo?

Ceniza
fuente

Respuestas:

930

La forma más fácil es usar sed (o perl):

sed -i -e 's/abc/XYZ/g' /tmp/file.txt

Lo que invocará a sed para hacer una edición in situ debido a la -iopción. Esto se puede llamar desde bash.

Si realmente quieres usar solo bash, entonces lo siguiente puede funcionar:

while read a; do
    echo ${a//abc/XYZ}
done < /tmp/file.txt > /tmp/file.txt.t
mv /tmp/file.txt{.t,}

Esto recorre cada línea, realiza una sustitución y escribe en un archivo temporal (no desea bloquear la entrada). El movimiento al final solo se mueve temporalmente al nombre original.

johnny
fuente
3
Excepto que invocar mv es casi tan 'no Bash' como usar sed. Casi dije lo mismo del eco, pero es una concha incorporada.
delgado
55
Sin embargo, el argumento -i para sed no existe para Solaris (y creo que algunas otras implementaciones), así que tenlo en cuenta. Acabo de pasar varios minutos descubriendo eso ...
Panky
2
Nota personal: sobre la expresión regular de sed: s/..../..../ - Substitutey /g - Global
suma de comprobación
89
Nota para los usuarios de Mac que reciben un invalid command code Cerror ... Para los reemplazos en el lugar, BSD sedrequiere una extensión de archivo después de la -imarca porque guarda un archivo de respaldo con la extensión dada. Por ejemplo: sed -i '.bak' 's/find/replace/' /file.txt puede omitir la copia de seguridad utilizando una cadena vacía como esta:sed -i '' 's/find/replace/' /file.txt
Austin
8
Sugerencia: si desea utilizar un dispositivo de reparación que no distinga entre mayúsculas y minúsculass/abc/XYZ/gi
Boris D. Teoharov el
166

La manipulación de archivos normalmente no es realizada por Bash, sino por programas invocados por Bash, por ejemplo:

perl -pi -e 's/abc/XYZ/g' /tmp/file.txt

La -ibandera le dice que haga un reemplazo en el lugar.

Consulte man perlrunpara obtener más detalles, incluido cómo realizar una copia de seguridad del archivo original.

Alnitak
fuente
37
El purista en mí dice que no puedes estar seguro de que Perl estará disponible en el sistema. Pero rara vez es el caso hoy en día. Quizás estoy mostrando mi edad.
delgado
3
¿Puedes mostrar un ejemplo más complejo? Algo así como reemplazar "chdir / blah" con "chdir / blah2". Lo intenté perl -pi -e 's/chdir (?:\\/[\\w\\.\\-]+)+/chdir blah/g' text, pero sigo obteniendo un error con No tener espacio entre el patrón y la siguiente palabra está en desuso en la línea -e 1. No coincide (en expresiones regulares; marcado por <- AQUÍ en m / (chdir) () (<- AQUÍ ?: \\ / at -e línea 1.
CMCDragonkai
@CMCDragonkai Comprueba esta respuesta: stackoverflow.com/a/12061491/2730528
Alfonso Santiago
69

Me sorprendió cuando tropecé con esto ...

Hay un replacecomando que se incluye con el "mysql-server"paquete, así que si lo ha instalado, pruébelo:

# replace string abc to XYZ in files
replace "abc" "XYZ" -- file.txt file2.txt file3.txt

# or pipe an echo to replace
echo "abcdef" |replace "abc" "XYZ"

Vea man replacepara más sobre esto.

rayro
fuente
12
Aquí hay dos cosas posibles: a) replacees una herramienta útil e independiente y la gente de MySQL debería liberarla por separado y depender de ella b) replacerequiere un poco de MySQL o_O De cualquier manera, instalar mysql-server para reemplazarlo sería lo incorrecto. :)
Philip Whitehouse
solo funciona para mac? en mi ubuntu I centos ese comando no existe
Paul
1
Eso es porque no tienes el mysql-serverpaquete instalado. Como señaló @rayro, replacees parte de ello.
Phius
2
"Advertencia: el reemplazo está en desuso y se eliminará en una versión futura".
Steven Vachon
1
¡Tenga cuidado de no ejecutar el comando REPLACE en Windows! En Windows, el comando REPLACE es para una replicación rápida de archivos. No es relevante para esta discusión.
Maor
53

Esta es una publicación antigua, pero para cualquiera que quiera usar variables como @centurian dijo que las comillas simples significan que nada se expandirá.

Una forma simple de obtener variables es hacer una concatenación de cadenas, ya que esto se hace por yuxtaposición en bash, lo siguiente debería funcionar:

sed -i -e "s/$var1/$var2/g" /tmp/file.txt
zcourts
fuente
39

Bash, como otros shells, es solo una herramienta para coordinar otros comandos. Por lo general, intentaría usar comandos UNIX estándar, pero, por supuesto, puede usar Bash para invocar cualquier cosa, incluidos sus propios programas compilados, otros scripts de shell, scripts de Python y Perl, etc.

En este caso, hay un par de formas de hacerlo.

Si desea leer un archivo y escribirlo en otro archivo, haciendo una búsqueda / reemplazo a medida que avanza, use sed:

sed 's/abc/XYZ/g' <infile >outfile

Si desea editar el archivo en su lugar (como si abriera el archivo en un editor, lo editara y luego lo guardara) proporcione instrucciones al editor de línea 'ex'

echo "%s/abc/XYZ/g
w
q
" | ex file

Ex es como vi sin el modo de pantalla completa. Puede darle los mismos comandos que usaría en el indicador ':' de vi.

Delgado
fuente
33

Encontré este hilo entre otros y estoy de acuerdo en que contiene las respuestas más completas, así que también agrego las mías:

  1. sedy edson muy útiles ... a mano. Mira este código de @Johnny:

    sed -i -e 's/abc/XYZ/g' /tmp/file.txt
  2. Cuando mi restricción es usarlo en un script de shell, no se puede usar ninguna variable dentro de "abc" o "XYZ". El BashFAQ parece estar de acuerdo con lo que entiendo por lo menos. Entonces, no puedo usar:

    x='abc'
    y='XYZ'
    sed -i -e 's/$x/$y/g' /tmp/file.txt
    #or,
    sed -i -e "s/$x/$y/g" /tmp/file.txt

    ¿pero que podemos hacer? Como, @Johnny dijo usar un while read...pero, desafortunadamente, ese no es el final de la historia. Lo siguiente funcionó bien conmigo:

    #edit user's virtual domain
    result=
    #if nullglob is set then, unset it temporarily
    is_nullglob=$( shopt -s | egrep -i '*nullglob' )
    if [[ is_nullglob ]]; then
       shopt -u nullglob
    fi
    while IFS= read -r line; do
       line="${line//'<servername>'/$server}"
       line="${line//'<serveralias>'/$alias}"
       line="${line//'<user>'/$user}"
       line="${line//'<group>'/$group}"
       result="$result""$line"'\n'
    done < $tmp
    echo -e $result > $tmp
    #if nullglob was set then, re-enable it
    if [[ is_nullglob ]]; then
       shopt -s nullglob
    fi
    #move user's virtual domain to Apache 2 domain directory
    ......
  3. Como se puede ver si nullglobestá configurado entonces, se comporta de manera extraña cuando hay una cadena que contiene un *como en:

    <VirtualHost *:80>
     ServerName www.example.com

    que se convierte

    <VirtualHost ServerName www.example.com

    no hay un ángulo angular final y Apache2 ni siquiera puede cargarse.

  4. Este tipo de análisis debería ser más lento que la búsqueda y el reemplazo de un solo clic, pero, como ya vio, hay cuatro variables para cuatro patrones de búsqueda diferentes que funcionan en un ciclo de análisis.

La solución más adecuada que se me ocurre con los supuestos del problema.

centurias
fuente
12
En tu (2), puedes hacerlo sed -e "s/$x/$y/", y funcionará. No las comillas dobles. Puede ser muy confuso si las cadenas de las variables contienen caracteres con un significado especial. Por ejemplo si x = "/" o x = "\". Cuando encuentre estos problemas, probablemente significa que debe dejar de intentar usar el shell para este trabajo.
delgado
Hola delgado, veo que también estás en contra de usar Perl. Cual es tu solucion ¡Porque de hecho quiero cambiar dinámicamente una ruta en un archivo, lo que significa que tengo mucho / en la cadena!
Mahdi
20

Puedes usar sed:

sed -i 's/abc/XYZ/gi' /tmp/file.txt

Úselo -ipara "ignorar mayúsculas y minúsculas" si no está seguro de que el texto para encontrar es abco ABCo AbC, etc.

Puede usar findy sedsi no conoce su nombre de archivo:

find ./ -type f -exec sed -i 's/abc/XYZ/gi' {} \;

Buscar y reemplazar en todos los archivos de Python:

find ./ -iname "*.py" -type f -exec sed -i 's/abc/XYZ/gi' {} \;
MMParvin
fuente
12

También puede usar el edcomando para realizar búsquedas en el archivo y reemplazar:

# delete all lines matching foobar 
ed -s test.txt <<< $'g/foobar/d\nw' 

Ver más en " Edición de archivos mediante scripts coned ".

el hombre de hojalata
fuente
3
Esta solución es independiente de las incompatibilidades de GNU / FreeBSD (Mac OSX) (a diferencia de sed -i <pattern> <filename>). ¡Muy agradable!
Peterino
11

Si el archivo en el que está trabajando no es tan grande, y almacenarlo temporalmente en una variable no es un problema, entonces puede usar la sustitución de cadenas Bash en todo el archivo de una vez; no hay necesidad de revisarlo línea por línea:

file_contents=$(</tmp/file.txt)
echo "${file_contents//abc/XYZ}" > /tmp/file.txt

Todo el contenido del archivo se tratará como una cadena larga, incluidos los saltos de línea.

XYZ puede ser una variable $replacement, por ejemplo , y una ventaja de no usar sed aquí es que no necesita preocuparse de que la cadena de búsqueda o reemplazo pueda contener el carácter delimitador de patrón sed (generalmente, pero no necesariamente, /). Una desventaja es no poder usar expresiones regulares o cualquiera de las operaciones más sofisticadas de sed.

johnraff
fuente
¿Algún consejo para usar esto con caracteres de tabulación? Por alguna razón, mi script no encuentra nada con pestañas después de cambiar de sed con mucho escape a este método.
Brian Hannay
2
Si desea poner una pestaña en la cadena que está reemplazando, puede hacerlo con la sintaxis "comillas simples" de Bash, de modo que una pestaña esté representada por $ '\ t', y puede hacer $ echo 'tab' $ '\ t''separated'> archivo de prueba; $ file_contents = $ (<archivo de prueba); $ echo "$ {file_contents // $ '\ t' / TAB}"; tabTABseparated `
johnraff
5

Pruebe el siguiente comando de shell:

find ./  -type f -name "file*.txt" | xargs sed -i -e 's/abc/xyz/g'
J Ajay
fuente
44
Esta es una excelente respuesta a "cómo accidentalmente también todos los archivos en todos los subdirectorios", pero eso no parece ser lo que se pregunta aquí.
tripleee
Esta sintaxis no funcionará para la versión BSD de sed, use sed -i''en su lugar.
kenorb
5

Para editar texto en el archivo de forma no interactiva, necesita un editor de texto en el lugar, como vim.

Aquí hay un ejemplo simple de cómo usarlo desde la línea de comandos:

vim -esnc '%s/foo/bar/g|:wq' file.txt

Esto es equivalente a la respuesta @slim del ex editor, que es básicamente lo mismo.

Aquí hay algunos exejemplos prácticos.

Reemplazar texto foocon baren el archivo:

ex -s +%s/foo/bar/ge -cwq file.txt

Eliminando espacios en blanco finales para múltiples archivos:

ex +'bufdo!%s/\s\+$//e' -cxa *.txt

Solución de problemas (cuando el terminal está atascado):

  • Agregue -V1param para mostrar mensajes detallados.
  • Forzar la salida de: -cwq!.

Ver también:

kenorb
fuente
Quería hacer los reemplazos de forma interactiva. Por lo tanto, probé "vim -esnc '% s / foo / bar / gc |: wq' file.txt". Pero la terminal está atascada ahora. ¿Cómo haremos los reemplazos interactivamente sin que el shell bash se comporte de manera extraña?
vineeshvs
Para depurar, agregue -V1, para forzar el cierre, use wq!.
kenorb
2

También puedes usar python dentro del script bash. No tuve mucho éxito con algunas de las mejores respuestas aquí, y descubrí que esto funciona sin la necesidad de bucles:

#!/bin/bash
python
filetosearch = '/home/ubuntu/ip_table.txt'
texttoreplace = 'tcp443'
texttoinsert = 'udp1194'

s = open(filetosearch).read()
s = s.replace(texttoreplace, texttoinsert)
f = open(filetosearch, 'w')
f.write(s)
f.close()
quit()
micalith
fuente
1

Puedes usar el comando rpl. Por ejemplo, desea cambiar el nombre de dominio en todo el proyecto php.

rpl -ivRpd -x'.php' 'old.domain.name' 'new.domain.name' ./path_to_your_project_folder/  

Esto no es una clara causa, pero es muy rápido y útil. :)

zalex
fuente