desarrollo-web-br-bd.com

¿Cómo puedo reemplazar una cadena en un archivo (s)?

Reemplazar cadenas en archivos basados ​​en ciertos criterios de búsqueda es una tarea muy común. Cómo puedo

  • reemplazar cadena foo con bar en todos los archivos en el 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 determinado contexto?
  • reemplazar si la cadena está en un cierto número de línea?
  • reemplazar múltiples cadenas con el mismo reemplazo
  • reemplazar múltiples cadenas con diferentes reemplazos
791
terdon

1. Reemplazar todas las ocurrencias 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.

Todas las soluciones sed en esta respuesta asumen GNU sed. Si usa FreeBSD o OS/X, reemplace _-i_ con _-i ''_. También tenga en cuenta que el uso del modificador _-i_ con cualquier versión de sed tiene cierto sistema de archivos implicaciones de seguridad y no es aconsejable en ningún script que planee distribuir de ninguna manera.

  • Archivos no recursivos en este directorio solamente:

    _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 y recursivos ( incluidos los ocultos ) en este y todos los subdirectorios

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

    Si está usando zsh:

    _sed -i -- 's/foo/bar/g' **/*(D.)
    _

    (puede fallar si la lista es demasiado grande, consulte zargs para evitar).

    Bash no puede verificar directamente los archivos normales, se necesita un bucle (las llaves evitan establecer las opciones a nivel mundial):

    _( 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 en este directorio solamente:

    _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 y 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á usando 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 archivos que comienzan con _-_.

  • Si un archivo es de cierto tipo, por ejemplo, ejecutable (vea _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

  • Reemplace foo con bar solo si hay un baz más adelante en la misma línea:

    _sed -i 's/foo\(.*baz\)/bar\1/' file
    _

    En sed, el uso de \( \) 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 tales expresiones regulares, consulte aquí .

  • Reemplace foo con bar solo si foo se encuentra en la columna 3d (campo) del archivo de entrada (suponiendo campos separados por espacios en blanco):

    _gawk -i inplace '{gsub(/foo/,"baz",$3); print}' file
    _

    (necesita gawk 4.1.0 o más reciente).

  • Para un campo diferente solo use _$N_ donde N 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 "@F\n"' foo 
    _

    NOTA: las soluciones awk y Perl afectarán el espaciado en el archivo (elimine los espacios en blanco iniciales y finales, y convierta secuencias de espacios en blanco en un carácter de espacio en las líneas que coincidan). Para un campo diferente, use _$F[N-1]_ donde N es el número de campo que desea y para un separador de campo diferente use (_$"=":"_ establece el separador de campo de salida en _:_) :

    _Perl -i -F':' -ane '$F[2]=~s/foo/baz/g; $"=":";print "@F"' foo 
    _
  • Reemplace foo con bar solo en la cuarta 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. Operaciones de reemplazo múltiple: reemplace con diferentes cadenas

  • Puede combinar los comandos sed:

    _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 de 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 archivo de script sed:

    _#! /usr/bin/sed -f
    s/foo/bar/g
    s/baz/zab/g
    _
  • O, 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 archivos de datos grandes, por lo que es posible que desee leer los patrones y crear un script sed a partir de ellos. Lo siguiente supone que un delimitador <space> separa una lista de MATCH <space> REPLACE pares que ocurren uno por línea en el archivo _patterns.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 <space> en cualquiera de [~ # ~] coincidencia [~ # ~] o [~ # ~] reemplazar [~ # ~]. Sin embargo, el método es muy general: básicamente, si puede crear una secuencia de salida que se parezca a una secuencia de comandos sed, entonces puede generar esa secuencia como una secuencia de comandos sed especificando sed el archivo de script como _-_ stdin.

  • Puede combinar y concatenar múltiples 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 debe terminar en una línea electrónica _\n_.

  • 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 la expresión regular ¡metacaracteres. Puedes hacer esto con bastante facilidad:

    _sed 's/[]$&^*\./[]/\\&/g
         s| *\([^ ]*\) *\([^ ]*\).*|s/\1/\2/g|
    ' <patterns.txt |
    sed -f- ./editfile >outfile
    _

5. Operaciones de reemplazo múltiple: reemplaza múltiples patrones con la misma cadena

  • Reemplace cualquiera de foo, bar o baz con foobar

    _sed -Ei 's/foo|bar|baz/foobar/g' file
    _
  • o

    _Perl -i -pe 's/foo|bar|baz/foobar/g' file
    _
1061
terdon

Una buena herramienta de Linux r e pl acement es rpl, originalmente escrito para el proyecto Debian, por lo que está disponible con apt-get install rpl en cualquier distribución derivada de Debian, y puede ser para otros, pero de lo contrario puede descargar el tar.gz archivo en SourgeForge .

El ejemplo más simple de uso:

 $ rpl old_string new_string test.txt

Tenga en cuenta que si la cadena contiene espacios, debe estar entre comillas. Por defecto rpl cuida las letras mayúsculas pero no las palabras completas , pero puede cambiar estos valores predeterminados con las opciones -i (ignorar mayúsculas y minúsculas) y -w (palabras completas). También puede especificar múltiples archivos :

 $ rpl -i -w "old string" "new string" test.txt test2.txt

O incluso especifique las extensiones (-x) para buscar o incluso buscar recursivamente (-R) en el directorio:

 $ rpl -x .html -x .txt -R old_string new_string test*

También puede buscar/reemplazar en modo interactivo con -p (Preguntar) opción:

El resultado muestra el número de archivos/cadenas reemplazados y el tipo de búsqueda (mayúsculas/minúsculas, palabras completas/parciales), pero puede ser silencioso con el -q ( opción de modo silencioso ), o incluso más detallado, enumerando los números de línea que contienen coincidencias de cada archivo y directorio con -v ( opción de modo detallado ).

Otras opciones que vale la pena recordar son -e (honor e escalas) que permiten regular expressions, para que puedas buscar también pestañas (\t), nuevas líneas (\n), etc. Incluso puedes usar -f para forzar permisos (por supuesto, solo cuando el usuario tiene permisos de escritura) y -d para preservar los tiempos de modificación`).

Finalmente, si no está seguro de cuál será exactamente, use -s ( modo de simulación ).

79
Fran

Cómo hacer una búsqueda y reemplazar varios archivos sugiere:

También podría 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 recorre el archivo de entrada, imprimiendo cada línea después de que se le aplica el script.

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' )

Usé esto:

grep -r "old_string" -l | tr '\n' ' ' | xargs sed -i 's/old_string/new_string/g'
  1. Lista todos los archivos que contienen old_string.

  2. Reemplace la nueva línea en el resultado con espacios (para que la lista de archivos se pueda alimentar a sed.

  3. Ejecute sed en esos archivos para reemplazar la cadena antigua por una nueva.

Actualización: El resultado anterior fallará en los nombres de archivo que contienen espacios en blanco. En cambio, use:

grep --null -lr "old_string" | xargs --null sed -i 's/old_string/new_string/g'

15
o_o_o--

Puede usar Vim en modo Ex:

reemplazar cadena ALF con BRA en todos los archivos en el directorio actual?

for CHA in *
do
  ex -sc '%s/ALF/BRA/g' -cx "$CHA"
done

hacer lo mismo recursivamente para subdirectorios?

find -type f -exec ex -sc '%s/ALF/BRA/g' -cx {} ';'

reemplazar solo si el nombre del archivo coincide con otra cadena?

for CHA in *.txt
do
  ex -sc '%s/ALF/BRA/g' -cx "$CHA"
done

reemplazar solo si la cadena se encuentra en un determinado contexto?

ex -sc 'g/DEL/s/ALF/BRA/g' -cx file

reemplazar si la cadena está en un cierto número de línea?

ex -sc '2s/ALF/BRA/g' -cx file

reemplazar múltiples cadenas con el mismo reemplazo

ex -sc '%s/\vALF|ECH/BRA/g' -cx file

reemplazar múltiples cadenas con diferentes reemplazos

ex -sc '%s/ALF/BRA/g|%s/FOX/GOL/g' -cx file
15
Steven Penny

Desde la perspectiva del usuario, una herramienta agradable y simple de Unix que hace el trabajo perfectamente es qsubst . Por ejemplo,

% qsubst foo bar *.c *.h

reemplazará foo con bar en todos mis archivos C. Una buena característica es que qsubst hará una consulta-reemplazar , es decir, me mostrará cada aparición de foo y pregunte si quiero reemplazarlo o no. [Puede reemplazar incondicionalmente (sin preguntar) con -go opción, y hay otras opciones, por ejemplo, -w si solo desea reemplazar foo cuando es una palabra completa.]

Cómo obtenerlo: qsubst fue inventado por der Mouse (de McGill) y publicado en comp.unix.sources 11 (7) en agosto de 1987. Existen versiones actualizadas. Por ejemplo, la versión de NetBSD qsubst.c,v 1.8 2004/11/01 compila y funciona perfectamente en mi mac.

7
phs

ripgrep (nombre del comando rg) es una herramienta grep, pero también admite la búsqueda y el reemplazo.

$ cat ip.txt
dark blue and light blue
light orange
blue sky
$ # by default, line number is displayed if output destination is stdout
$ # by default, only lines that matched the given pattern is displayed
$ # 'blue' is search pattern and -r 'red' is replacement string
$ rg 'blue' -r 'red' ip.txt
1:dark red and light red
3:red sky

$ # --passthru option is useful to print all lines, whether or not it matched
$ # -N will disable line number prefix
$ # this command is similar to: sed 's/blue/red/g' ip.txt
$ rg --passthru -N 'blue' -r 'red' ip.txt
dark red and light red
light orange
red sky


rg no admite la opción en el lugar, por lo que deberá hacerlo usted mismo

$ # -N isn't needed here as output destination is a file
$ rg --passthru 'blue' -r 'red' ip.txt > tmp.txt && mv tmp.txt ip.txt
$ cat ip.txt
dark red and light red
light orange
red sky


Ver Rust regex documentation para la sintaxis y características de expresiones regulares. El interruptor -P Habilitará PCRE2 sabor. rg admite Unicode de forma predeterminada.

$ # non-greedy quantifier is supported
$ echo 'food land bark sand band cue combat' | rg 'foo.*?ba' -r 'X'
Xrk sand band cue combat

$ # unicode support
$ echo 'fox:αλεπού,eagle:αετός' | rg '\p{L}+' -r '($0)'
(fox):(αλεπού),(eagle):(αετός)

$ # set operator example, remove all punctuation characters except . ! and ?
$ para='"Hi", there! How *are* you? All fine here.'
$ echo "$para" | rg '[[:punct:]--[.!?]]+' -r ''
Hi there! How are you? All fine here.

$ # use -P if you need even more advanced features
$ echo 'car bat cod map' | rg -P '(bat|map)(*SKIP)(*F)|\w+' -r '[$0]'
[car] bat [cod] map


Al igual que grep, la opción -F Permitirá que las cadenas fijas coincidan, una opción útil que creo que sed también debería implementar.

$ printf '2.3/[4]*6\nfoo\n5.3-[4]*9\n' | rg --passthru -F '[4]*' -r '2'
2.3/26
foo
5.3-29


Otra opción útil es -U Que permite la coincidencia multilínea

$ # (?s) flag will allow . to match newline characters as well
$ printf '42\nHi there\nHave a Nice Day' | rg --passthru -U '(?s)the.*ice' -r ''
42
Hi  Day


rg también puede manejar archivos de estilo dos

$ # same as: sed -E 's/\w+(\r?)$/123\1/'
$ printf 'hi there\r\ngood day\r\n' | rg --passthru --crlf '\w+$' -r '123'
hi 123
good 123


Otra ventaja de rg es que es probable que sea más rápido que sed

$ # for small files, initial processing time of rg is a large component
$ time echo 'aba' | sed 's/a/b/g' > f1
real    0m0.002s
$ time echo 'aba' | rg --passthru 'a' -r 'b' > f2
real    0m0.007s

$ # for larger files, rg is likely to be faster
$ # 6.2M sample ASCII file
$ wget https://norvig.com/big.txt    
$ time LC_ALL=C sed 's/\bcat\b/dog/g' big.txt > f1
real    0m0.060s
$ time rg --passthru '\bcat\b' -r 'dog' big.txt > f2
real    0m0.048s
$ diff -s f1 f2
Files f1 and f2 are identical

$ time LC_ALL=C sed -E 's/\b(\w+)(\s+\1)+\b/\1/g' big.txt > f1
real    0m0.725s
$ time rg --no-pcre2-unicode --passthru -wP '(\w+)(\s+\1)+' -r '$1' big.txt > f2
real    0m0.093s
$ diff -s f1 f2
Files f1 and f2 are identical
3
Sundeep

Necesitaba algo que proporcionara una opción de ejecución en seco y funcionara recursivamente con un glob, y después de intentar hacerlo con awk y sed me di por vencido y lo hice en python.

El script busca recursivamente todos los archivos que coinciden con un patrón global (por ejemplo, --glob="*.html") para una expresión regular y reemplaza con la expresión regular de reemplazo:

find_replace.py [--dir=my_folder] \
    --search-regex=<search_regex> \
    --replace-regex=<replace_regex> \
    --glob=[glob_pattern] \
    --dry-run

Cada opción larga como --search-regex tiene una opción corta correspondiente, es decir, -s. Corre con -h para ver todas las opciones.

Por ejemplo, esto cambiará todas las fechas de 2017-12-31 a 31-12-2017:

python replace.py --glob=myfile.txt \
    --search-regex="(\d{4})-(\d{2})-(\d{2})" \
    --replace-regex="\3-\2-\1" \
    --dry-run --verbose
import os
import fnmatch
import sys
import shutil
import re

import argparse

def find_replace(cfg):
    search_pattern = re.compile(cfg.search_regex)

    if cfg.dry_run:
        print('THIS IS A DRY RUN -- NO FILES WILL BE CHANGED!')

    for path, dirs, files in os.walk(os.path.abspath(cfg.dir)):
        for filename in fnmatch.filter(files, cfg.glob):

            if cfg.print_parent_folder:
                pardir = os.path.normpath(os.path.join(path, '..'))
                pardir = os.path.split(pardir)[-1]
                print('[%s]' % pardir)
            filepath = os.path.join(path, filename)

            # backup original file
            if cfg.create_backup:
                backup_path = filepath + '.bak'

                while os.path.exists(backup_path):
                    backup_path += '.bak'
                print('DBG: creating backup', backup_path)
                shutil.copyfile(filepath, backup_path)

            with open(filepath) as f:
                old_text = f.read()

            all_matches = search_pattern.findall(old_text)

            if all_matches:

                print('Found {} matches in file {}'.format(len(all_matches), filename))

                new_text = search_pattern.sub(cfg.replace_regex, old_text)

                if not cfg.dry_run:
                    with open(filepath, "w") as f:
                        print('DBG: replacing in file', filepath)
                        f.write(new_text)
                else:
                    for idx, matches in enumerate(all_matches):
                        print("Match #{}: {}".format(idx, matches))

                    print("NEW TEXT:\n{}".format(new_text))

            Elif cfg.verbose:
                print('File {} does not contain search regex "{}"'.format(filename, cfg.search_regex))


if __name__ == '__main__':

    parser = argparse.ArgumentParser(description='''DESCRIPTION:
    Find and replace recursively from the given folder using regular expressions''',
                                     formatter_class=argparse.RawDescriptionHelpFormatter,
                                     epilog='''USAGE:
    {0} -d [my_folder] -s <search_regex> -r <replace_regex> -g [glob_pattern]

    '''.format(os.path.basename(sys.argv[0])))

    parser.add_argument('--dir', '-d',
                        help='folder to search in; by default current folder',
                        default='.')

    parser.add_argument('--search-regex', '-s',
                        help='search regex',
                        required=True)

    parser.add_argument('--replace-regex', '-r',
                        help='replacement regex',
                        required=True)

    parser.add_argument('--glob', '-g',
                        help='glob pattern, i.e. *.html',
                        default="*.*")

    parser.add_argument('--dry-run', '-dr',
                        action='store_true',
                        help="don't replace anything just show what is going to be done",
                        default=False)

    parser.add_argument('--create-backup', '-b',
                        action='store_true',
                        help='Create backup files',
                        default=False)

    parser.add_argument('--verbose', '-v',
                        action='store_true',
                        help="Show files which don't match the search regex",
                        default=False)

    parser.add_argument('--print-parent-folder', '-p',
                        action='store_true',
                        help="Show the parent info for debug",
                        default=False)

    config = parser.parse_args(sys.argv[1:])

    find_replace(config)

Here es una versión actualizada del script que resalta los términos de búsqueda y los reemplazos con diferentes colores.

3
ccpizza