desenv-web-rp.com

Como posso substituir uma string em um arquivo?

Substituir seqüências de caracteres em arquivos com base em determinados critérios de pesquisa é uma tarefa muito comum. Como posso

  • substituir string foo por bar em todos os arquivos no diretório atual?
  • fazer o mesmo recursivamente para subdiretórios?
  • substituir apenas se o nome do arquivo corresponder a outra sequência?
  • substituir apenas se a string for encontrada em um determinado contexto?
  • substituir se a string estiver em um determinado número de linha?
  • substituir várias strings pela mesma substituição
  • substituir várias strings por diferentes substituições
791
terdon

1. Substituindo todas as ocorrências de uma sequência por outra em todos os arquivos no diretório atual:

Estes são os casos em que você sabe que o diretório contém apenas arquivos regulares e que deseja processar todos os arquivos não ocultos. Se não for esse o caso, use as abordagens em 2.

Todas as soluções sed nesta resposta assumem GNU sed. Se estiver usando o FreeBSD ou OS/X, substitua -i por -i ''. Observe também que o uso da opção -i com qualquer versão de sed possui um certo sistema de arquivos implicações de segurança e é desaconselhável em qualquer script que você planeja distribuir em de qualquer forma.

  • Arquivos não recursivos, apenas neste diretório:

    sed -i -- 's/foo/bar/g' *
    Perl -i -pe 's/foo/bar/g' ./* 
    

    (o Perl one falhará nos nomes de arquivos que terminam em | ou espaço) ).

  • Arquivos regulares e recursivos (, incluindo arquivos ocultos ) neste e em todos os subdiretórios

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

    Se você estiver usando o zsh:

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

    (pode falhar se a lista for muito grande, consulte zargs para contornar o problema).

    O Bash não pode verificar diretamente se há arquivos regulares, é necessário um loop (chaves evitam definir as opções globalmente):

    ( shopt -s globstar dotglob;
        for file in **; do
            if [[ -f $file ]] && [[ -w $file ]]; then
                sed -i -- 's/foo/bar/g' "$file"
            fi
        done
    )
    

    Os arquivos são selecionados quando são arquivos reais (-f) e são graváveis ​​(-w).

2. Substitua apenas se o nome do arquivo corresponder a outra sequência/tiver uma extensão específica/for de um determinado tipo, etc:

  • Arquivos não recursivos, apenas neste diretório:

    sed -i -- 's/foo/bar/g' *baz*    ## all files whose name contains baz
    sed -i -- 's/foo/bar/g' *.baz    ## files ending in .baz
    
  • Arquivos regulares e recursivos neste e em todos os subdiretórios

    find . -type f -name "*baz*" -exec sed -i 's/foo/bar/g' {} +
    

    Se você estiver usando o bash (chaves, evite definir as opções globalmente):

    ( shopt -s globstar dotglob
        sed -i -- 's/foo/bar/g' **baz*
        sed -i -- 's/foo/bar/g' **.baz
    )
    

    Se você estiver usando o zsh:

    sed -i -- 's/foo/bar/g' **/*baz*(D.)
    sed -i -- 's/foo/bar/g' **/*.baz(D.)
    

    O -- serve para informar sed que não haverá mais sinalizadores na linha de comando. Isso é útil para proteger contra nomes de arquivos começando com -.

  • Se um arquivo é de um determinado tipo, por exemplo, executável (consulte man find para obter mais opções):

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

    zsh:

    sed -i -- 's/foo/bar/g' **/*(D*)
    

3. Substitua apenas se a sequência for encontrada em um determinado contexto

  • Substitua foo por bar somente se houver um baz posteriormente na mesma linha:

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

    Em sed, usar \( \) salva o que estiver entre parênteses e você pode acessá-lo com \1. Existem muitas variações desse tema. Para saber mais sobre essas expressões regulares, consulte aqui .

  • Substitua foo por bar somente se foo for encontrado na coluna 3d (campo) do arquivo de entrada (assumindo campos separados por espaços em branco):

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

    (precisa gawk 4.1.0 ou mais recente).

  • Para um campo diferente, use $N onde N é o número do campo de interesse. Para um separador de campo diferente (: neste exemplo), use:

    gawk -i inplace -F':' '{gsub(/foo/,"baz",$3);print}' file
    

    Outra solução usando Perl:

    Perl -i -ane '$F[2]=~s/foo/baz/g; $" = " "; print "@F\n"' foo 
    

    NOTA: as soluções awk e Perl afetarão o espaçamento no arquivo (remova os espaços em branco à esquerda e à direita e converta sequências de espaços em branco em um caractere de espaço nas linhas correspondentes). Para um campo diferente, use $F[N-1] onde N é o número do campo que você deseja e para um separador de campo diferente use (o $"=":" define o separador de campo de saída como : ):

    Perl -i -F':' -ane '$F[2]=~s/foo/baz/g; $"=":";print "@F"' foo 
    
  • Substitua foo por bar apenas na quarta linha:

    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. Operações de substituição múltipla: substitua por cordas diferentes

  • Você pode combinar os comandos sed:

    sed -i 's/foo/bar/g; s/baz/zab/g; s/Alice/Joan/g' file
    

    Esteja ciente de que a ordem é importante (sed 's/foo/bar/g; s/bar/baz/g' substituirá foo por baz).

  • ou comandos Perl

    Perl -i -pe 's/foo/bar/g; s/baz/zab/g; s/Alice/Joan/g' file
    
  • Se você possui um grande número de padrões, é mais fácil salvar seus padrões e suas substituições em um arquivo de script sed:

    #! /usr/bin/sed -f
    s/foo/bar/g
    s/baz/zab/g
    
  • Ou, se você tiver muitos pares de padrões para que o acima seja possível, você poderá ler pares de padrões de um arquivo (dois padrões separados por espaço, $ pattern e $ Replacement, por linha):

    while read -r pattern replacement; do   
        sed -i "s/$pattern/$replacement/" file
    done < patterns.txt
    
  • Isso será muito lento para longas listas de padrões e grandes arquivos de dados, portanto, você pode querer ler os padrões e criar um script sed a partir deles. O seguinte pressupõe que um delimitador <espaço> separa uma lista de pares MATCH <espaço> REPLACE pares que ocorrem um por linha no arquivo patterns.txt:

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

    O formato acima é amplamente arbitrário e, por exemplo, não permite a <espaço> em qualquer um dos [~ # ~] correspondências [~ # ~] ou [~ # ~] substitua [~ # ~]. Porém, o método é muito geral: basicamente, se você pode criar um fluxo de saída que se parece com um script sed, pode originar esse fluxo como um script sed especificando sed como -stdin.

  • Você pode combinar e concatenar vários scripts da mesma maneira:

    SOME_PIPELINE |
    sed -e'#some expression script'  \
        -f./script_file -f-          \
        -e'#more inline expressions' \
    ./actual_edit_file >./outfile
    

    Um POSIX sed concatenará todos os scripts em um na ordem em que aparecem na linha de comando. Nada disso precisa terminar em um \newline.

  • grep pode funcionar da mesma maneira:

    sed -e'#generate a pattern list' <in |
    grep -f- ./grepped_file
    
  • Ao trabalhar com seqüências fixas como padrões, é uma boa prática escapar da expressão regular metacaracteres. Você pode fazer isso facilmente:

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

5. Operações de substituição múltipla: substitua vários padrões pela mesma sequência

  • Substitua qualquer um dos foo, bar ou baz por foobar

    sed -Ei 's/foo|bar|baz/foobar/g' file
    
  • ou

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

Uma boa ferramenta r e pl para Linux é rpl, que foi originalmente escrito para o projeto Debian, portanto está disponível com apt-get install rpl em qualquer distribuição derivada do Debian e pode ser para outras pessoas, mas, caso contrário, você pode fazer o download do tar.gz arquivo em SourgeForge .

Exemplo mais simples de uso:

 $ rpl old_string new_string test.txt

Observe que se a sequência contiver espaços, ela deverá ser colocada entre aspas. Por padrão rpl cuide de letras maiúsculas , mas não de palavras completas , mas você pode alterar esses padrões com as opções -i (ignorar maiúsculas e minúsculas) e -w (palavras inteiras). Você também pode especificar vários arquivos :

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

Ou até mesmo especificar as extensões (-x) para pesquisar ou mesmo pesquisar recursivamente (-R) no diretório:

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

Você também pode procurar/substituir no modo interativo por -p (Prompt) opção:

A saída mostra o número de arquivos/string substituídos e o tipo de pesquisa (maiúsculas/minúsculas, palavras inteiras/parciais), mas pode ficar em silêncio com os -q ( modo silencioso ), ou ainda mais detalhado, listando números de linhas que contêm correspondências de cada arquivo e diretório com -v ( opção detalhada do modo ).

Outras opções que valem a pena lembrar são -e (honra e escopos) que permitem regular expressions, para que você possa pesquisar também as guias (\t), novas linhas (\n), etc. Até você pode usar -f para forçar permissões (é claro, somente quando o usuário tiver permissões de gravação) e -d para preservar os tempos de modificação ").

Por fim, se você não tiver certeza sobre o que fará exatamente, use o -s ( modo de simulação ).

79
Fran

Como fazer uma pesquisa e substituir por vários arquivos sugere:

Você também pode usar o find e o sed, mas acho que essa pequena linha do Perl funciona bem.

Perl -pi -w -e 's/search/replace/g;' *.php
  • -e significa executar a seguinte linha de código.
  • -i significa editar no local
  • -w escrever avisos
  • -p loop sobre o arquivo de entrada, imprimindo cada linha após a aplicação do script.

Meus melhores resultados vêm do uso de Perl e grep (para garantir que o arquivo tenha a expressão de pesquisa)

Perl -pi -w -e 's/search/replace/g;' $( grep -rl 'search' )
26
Alejandro Salamanca Mazuelo

Eu usei isso:

grep -r "old_string" -l | tr '\n' ' ' | xargs sed -i 's/old_string/new_string/g'
  1. Listar todos os arquivos que contêm old_string.

  2. Substitua nova linha no resultado por espaços (para que a lista de arquivos possa ser alimentada para sed.

  3. Execute sed nesses arquivos para substituir a string antiga por nova.

Atualização: O resultado acima falhará nos nomes de arquivos que contêm espaços em branco. Em vez disso, use:

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

15
o_o_o--

Você pode usar o Vim no modo Ex:

substituir string ALF por BRA em todos os arquivos no diretório atual?

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

fazer o mesmo recursivamente para subdiretórios?

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

substituir apenas se o nome do arquivo corresponder a outra sequência?

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

substituir apenas se a string for encontrada em um determinado contexto?

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

substituir se a string estiver em um determinado número de linha?

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

substituir várias strings pela mesma substituição

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

substituir várias strings por diferentes substituições

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

Da perspectiva do usuário, uma ferramenta Unix agradável e simples que faz o trabalho perfeitamente é qsubst . Por exemplo,

% qsubst foo bar *.c *.h

substituirá foo por bar em todos os meus arquivos C. Um recurso interessante é que qsubst fará uma consulta-substituição , ou seja, ele me mostrará cada ocorrência de foo e pergunte se quero substituí-lo ou não. [Você pode substituir incondicionalmente (sem perguntar) por -go opção e existem outras opções, por exemplo, -w se você deseja substituir apenas foo quando se trata de uma palavra inteira.]

Como obtê-lo: qsubst foi inventado por der Mouse (da McGill) e publicado em comp.unix.sources 11 (7) em agosto de 1987. Existem versões atualizadas. Por exemplo, a versão NetBSD qsubst.c,v 1.8 2004/11/01 compila e roda perfeitamente no meu mac.

7
phs

ripgrep (nome do comando rg) é uma ferramenta grep, mas também suporta pesquisa e substituição.

$ 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 não suporta a opção no local, então você terá que fazer isso sozinho

$ # -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


Consulte documentação do Rex regex para obter sintaxe e recursos para expressões regulares. O -P switch ativará PCRE2 sabor. rg suporta Unicode por padrão.

$ # 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


Como grep, o -F opção permitirá que strings fixas sejam correspondidas, uma opção útil que eu acho que sed deve implementar também.

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


Outra opção útil é -U que permite a correspondência multilinha

$ # (?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 também pode lidar com arquivos no 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


Outra vantagem de rg é que é provável que seja mais 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

Eu precisava de algo que oferecesse uma opção de execução a seco e funcionasse recursivamente com uma glob, e depois de tentar fazer isso com awk e sed desisti e, em vez disso, fiz isso em python.

O script pesquisa recursivamente todos os arquivos que correspondem a um padrão global (por exemplo, --glob="*.html") para uma regex e substitui pela regex de substituição:

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

Todas as opções longas, como --search-regex tem uma opção curta correspondente, ou seja, -s. Correr com -h para ver todas as opções.

Por exemplo, isso inverte todas as datas de 2017-12-31 para 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 é uma versão atualizada do script que destaca os termos e substituições da pesquisa com cores diferentes.

3
ccpizza