Como substituir uma string em vários arquivos na linha de comando do linux

461

Preciso substituir uma string em muitos arquivos em uma pasta, apenas com sshacesso ao servidor. Como posso fazer isso?

mridul4c
fonte

Respostas:

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

Ocorrências de "foo" serão substituídas por "bar".

Em sistemas BSD como o macOS, você precisa fornecer uma extensão de backup como -i '.bak'"risco de corrupção ou conteúdo parcial" por página de manual.

cd /path/to/your/folder
sed -i '.bak' 's/foo/bar/g' *
kev
fonte
13
Isso não parece funcionar para mim se a string tiver espaços em branco ou caracteres especiais. Alguma idéia do porquê disso, ou preciso escapar de alguma forma? Obrigado!
Matthew Herbst
13
Pelo menos para a minha versão de sed, -irequer uma sequência após ela, que é anexada aos nomes de arquivos antigos. Então, sed -i .bak 's/foo/bar/g' *.xxmove todos os .xxarquivos para o .xx.baknome equivalente e gera os .xxarquivos com a substituição foo → bar.
Anaphory
24
Se alguém quer olhar para mais opções, há uma resposta na troca unix pilha que cobre mais uso casos local unix.stackexchange.com/a/112024/13488
Reddy
19
@MatthewHerbst para escapar de espaços, use \ like sed -i 's/foo\ with\ spaces/bar/g' *. Suponho que você tenha descoberto depois de tanto tempo ... mas dessa forma fica para outras pessoas que encontram o mesmo problema.
precisa saber é o seguinte
5
Para algo recursivo, você pode tentar o seguinte. Observe que não funciona se a lista de arquivos for enorme. sed -i -e 's/foo/bar/g' $(find /home/user/base/dir)
Scot
243

Semelhante à resposta de Kaspar, mas com o sinalizador g para substituir todas as ocorrências em uma linha.

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

Para caso global insensitivo:

find ./ -type f -exec sed -i 's/string1/string2/gI' {} \;
Céline Aussourd
fonte
23
Se você estiver em OSX e seus padrões pode conter pontos e quer a substituição no local (nenhum arquivo de backup), você deve usar LC_ALL=C find ./ -type f -exec sed -i '' -e 's/proc.priv/priv/g' {} \;(ver este post e este )
Jonathan H
2
Definitivamente, isso funcionou para mim, pois permite a filtragem com -name "* .js" '
Marcello de Sales
1
Uma opção detalhada seria legal, mas você pode apenas re-grep para ver se as alterações foram feitas. Nota: Para curingas, tente '-name "* .php"' e grep é ruim com recursão e curingas, é necessário adicionar --include=*.whatevercom -r
PJ Brunet
12
Não faça isso a partir da raiz de um repositório Git com check-out. Você pode acidentalmente corromper seu banco de dados no .git/. Certifique-se de cddescer para um nível mais baixo.
21717 Erikprice
1
Eu só fiz isso no meu repo git e agora git statusretorna: error: bad index file sha1 signature.... fatal: index file corrupt. O que da?
187 Jin Jin
165

A resposta do @ kev é boa, mas afeta apenas os arquivos no diretório imediato. O exemplo abaixo usa grep para localizar arquivos recursivamente. Funciona para mim sempre.

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

Divisão de comando

grep -r : --recursive , leia recursivamente todos os arquivos em cada diretório.
grep -l : --print-with-fósforos , imprime o nome de cada arquivo que possui uma correspondência, em vez de imprimir linhas correspondentes.
grep -i : --ignore-case .

xargs : transforme o STDIN em argumentos, siga esta resposta .
O comando xargs -i @ ~ contém @ ~ : um espaço reservado para o argumento a ser usado em uma posição específica no ~ command ~ , o sinal @ é um espaço reservado que pode ser substituído por qualquer string.

sed -i : edita arquivos no local, sem backups.
sed s / regexp / Replacement / : substitui a sequência correspondente de regexp pela substituição .
sed s / regexp / replace / g : global , faça a substituição para cada partida em vez de apenas a primeira partida.

pymarco
fonte
13
isso não funcionou para mim, mas funcionou:grep --include={*.php,*.html,*.js} -rnl './' -e "old-word" | xargs -i@ sed -i 's/old-word/new-word/g' @
Dennis
7
@pymarco Você pode explicar esse comando? Não sei por que você teve que usar xargs em vez de apenas usar sed depois |, também, por que -ino comando xargs? Eu li no manual que está obsoleto e deve usá-lo -I. E é @usado como um delimitador para início e fim para padrão?
Edson Horacio Junior
2
a questão é especificamente para linux.
Pymarco # 25/16
6
No osx, está tudo bem:grep -rli 'old-word' * | xargs -I@ sed -i '' 's/2.0.0/latest/g' @
Philippe Sultan
1
Seria bom se você quebrou algumas das opções ( rli) e do @sinal, por exemplo
Roymunson
37

Isso funcionou para mim:

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

Howerver, este não fez: sed -i 's/string1/string2/g' *. Talvez "foo" não tenha sido string1 e "bar" não string2.

Kaspar L. Palgi
fonte
1
Isso ocorre porque o sed trata o curinga * de maneira diferente. [abc] * significa um número arbitrário de caracteres do conjunto {a, b, c}. [a-z0-9] * funciona de forma semelhante ao curinga *.
thepiercingarrow
8
No uso do OSX:find ./ -type f -exec sed -i '' -e 's/string1/string2/' {} \;
Shaheen Ghiassy 16/04
32

Existem algumas respostas padrão para isso já listadas. Geralmente, você pode usar o find para listar recursivamente os arquivos e depois executar as operações com sed ou perl .

Para usos mais rápidos, você pode achar que o comando rpl é muito mais fácil de lembrar. Aqui está a substituição (foo -> bar), recursivamente em todos os arquivos:

rpl -R foo bar .

Você provavelmente precisará instalá-lo ( apt-get install rplou similar).

No entanto, para trabalhos mais difíceis que envolvem expressões regulares e substituição reversa ou renomeação de arquivos, além de pesquisar e substituir, a ferramenta mais geral e poderosa de que conheço é a repren , um pequeno script Python que escrevi há algum tempo. tarefas de renomeação e refatoração mais espinhosas. Os motivos pelos quais você pode preferir são:

  • Suporte para renomear arquivos, bem como pesquisar e substituir no conteúdo do arquivo.
  • Veja as alterações antes de se comprometer em executar a pesquisa e substituir.
  • Suporte expressões regulares com substituição traseira, palavras inteiras, sem distinção entre maiúsculas e minúsculas e modos de preservação de maiúsculas (substitua os modos foo -> bar, Foo -> bar, FOO -> BAR).
  • Funciona com várias substituições, incluindo trocas (foo -> bar e bar -> foo) ou conjuntos de substituições não exclusivas (foo -> bar, f -> x).

Para usá-lo pip install repren. Verifique o README para exemplos.

jlevy
fonte
1
Uau, repren é excelente! Apenas usei para alterar parte de uma palavra dentro de nomes de classe, métodos e variáveis, enquanto renomeia arquivos para coincidir com mais de 1.000 arquivos de cabeçalho e de origem C ++ e funcionou perfeitamente na primeira vez com um comando. Obrigado!
Bob Kocisko 1/11
29

Para substituir uma sequência em vários arquivos, você pode usar:

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

Por exemplo

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

Blog de origem

Djacomo
fonte
20

Para substituir um caminho nos arquivos (evitando caracteres de escape), você pode usar o seguinte comando:

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

O sinal @ significa que todos os caracteres especiais devem ser ignorados na seguinte sequência.

Ilya Suzdalnitski
fonte
2
Exatamente o que eu estava procurando em todas as outras respostas. Ou seja, como lidar com caracteres especiais, como ao alterar uma string que é um caminho. Obrigado. Parecia uma grande supervisão nas outras respostas.
Inspirado #
19

Caso sua string tenha uma barra (/), você poderá alterar o delimitador para '+'.

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

Este comando seria executado recursivamente no diretório atual.

gopiariv
fonte
Obrigado! Ajudou a mudar um monte de referências ../domain.comparadomain.com
Jack Rogers
12

As ocorrências da primeira linha de "foo" serão substituídas por "bar". E você pode usar a segunda linha para verificar.

grep -rl 'foo' . | xargs sed -i 's/foo/bar/g'
grep 'foo' -r * | awk -F: {'print $1'} | sort -n | uniq -c
jiasir
fonte
6
Por que você está vinculando ao seu blog? Ele contém exatamente o mesmo texto que sua resposta.
DavidPostill
1
eu gosto #grep -rl 'foo' . | xargs sed -i 's/foo/bar/g'
Prachi
10

Se você tiver uma lista de arquivos, poderá usar

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

Se você tiver todos os arquivos, poderá usar

replace "old_string" "new_string" -- *

Se você tiver uma lista de arquivos com extensão, poderá usar

replace "old_string" "new_string" -- *.extension
Pawel Dubiel
fonte
2
na verdade, "--file" deve ser apenas "-", pelo menos na minha versão
simpleuser 7/14
4
Este utilitário é distribuído dentro dos pacotes MySQL.
Dmitry Ginzburg
2
Embora eu gostaria de apreciar a solução, que funciona sem citar a linha regular como expressão regular.
Dmitry Ginzburg
1
Isso funciona bem - e se você quiser substituir todas as cordas em vários arquivos, que terminam em, por exemplo ".txt", você pode apenas fazerreplace "old_string" "new_string" -- *.txt
tsveti_iko
1
É melhor adicionar de onde obter esse replaceutilitário. Sem essas informações, essa resposta está incompleta.
Sitesh
8

"Você também pode usar o find e o sed, mas acho que essa pequena linha de 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

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

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' )
Alejandro Salamanca Mazuelo
fonte
8

Como você deseja procurar a sequência searche substituí-la replacepor vários arquivos, esta é minha fórmula de uma linha testada em batalha :

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

Explicação rápida do grep:

  • -R - pesquisa recursiva
  • -i - não diferencia maiúsculas de minúsculas
  • -I - pule arquivos binários (você quer texto, certo?)
  • -l- imprima uma lista simples como saída. Necessário para os outros comandos

A saída grep é então canalizada para sed (através de xargs), que é usada para substituir o texto. A -ibandeira alterará o arquivo diretamente. Remova-o para uma espécie de modo "funcionamento a seco".

Ignorante
fonte
4

Eu inventei minha própria solução antes de encontrar esta pergunta (e respostas). Procurei por diferentes combinações de "substituir" "vários" e "xml", porque esse era meu aplicativo, mas não encontrei esse em particular.

Meu problema: tive arquivos xml de primavera com dados para casos de teste, contendo objetos complexos. Um refator no código-fonte java alterou muitas classes e não se aplicava aos arquivos de dados xml. Para salvar os dados dos casos de teste, eu precisava alterar todos os nomes de classe em todos os arquivos xml, distribuídos em vários diretórios. Tudo isso enquanto salvava cópias de segurança dos arquivos xml originais (embora isso não fosse obrigatório, pois o controle de versão me salvaria aqui).

Eu estava procurando por uma combinação de find+ sed, porque funcionou para mim em outras situações, mas não com várias substituições ao mesmo tempo.

Então eu encontrei a resposta ask ubuntu e isso me ajudou a construir minha linha de comando:

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

E funcionou perfeitamente (bem, meu caso teve seis substituições diferentes). Mas observe que ele tocará em todos os arquivos * .xml no diretório atual. Por esse motivo, e se você é responsável por um sistema de controle de versão, convém filtrar primeiro e transmitir apenas aos sedque realmente possuem as strings que deseja; gostar:

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
fonte
e para o windows, acabei de descobrir que existe uma maneira de encontrar a string - ainda não checou como substituí-la - em um 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
fonte
3

Realmente idiota, mas não consegui que nenhum dos comandos sed funcionasse corretamente no OSX, então fiz essa coisa idiota:

:%s/foo/bar/g
:wn

^ - copie essas três linhas na minha área de transferência (sim, inclua a nova linha final) e, em seguida:

vi *

e mantenha pressionado o comando-v até que não exista nenhum arquivo.

Burro ... hacky ... eficaz ...

Justin
fonte
3

Em um MacBook Pro, usei o seguinte (inspirado em https://stackoverflow.com/a/19457213/6169225 ):

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

-i '' garantirá que você não esteja fazendo backups.

-e para regex moderno.

Marquistador
fonte
3
-eapenas diz ao sed que o próximo token é um comando. Para expressões regulares estendidas, use em seu -Elugar.
Benjamin W.
2

O editor de fluxo modifica vários arquivos "no local" quando chamado com o -icomutador, o que leva um arquivo de backup que termina como argumento. assim

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

substitui foopor bartodos os arquivos nesta pasta, mas não desce em subpastas. No entanto, isso irá gerar um novo .bakarquivo para cada arquivo no seu diretório. Para fazer isso recursivamente para todos os arquivos neste diretório e todos os seus subdiretórios, você precisa de um ajudante, como find, para percorrer a árvore de diretórios.

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

findpermite restrições adicionais sobre quais arquivos modificar, especificando outros argumentos como find ./ -name '*.php' -or -name '*.html' -print0, se necessário.


Nota: O GNU sednão requer um final de arquivo, sed -i 's/foo/bar/g' *também funcionará; O FreeBSD sedexige uma extensão, mas permite um espaço intermediário, assim sed -i .bak s/foo/bar/g *funciona.

Anaphory
fonte
2

script para comando multiedit

multiedit [-n PATTERN] OLDSTRING NEWSTRING

Com a resposta de Kaspar, criei um script bash para aceitar argumentos da linha de comando e, opcionalmente, limitar os nomes de arquivos correspondentes a um padrão. Salve no seu $ PATH e torne o executável; em seguida, basta usar o comando acima.

Aqui está o script:

#!/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 do Windows
fonte
1

Se o arquivo contiver barras invertidas (caminhos geralmente), você pode tentar algo como isto:

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

ex:

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

Para manter meu nó pessoal em inglês, escrevi um script utilitário que ajuda a substituir vários pares de strings novos / antigos por todos os arquivos em um diretório recursivamente.

O par múltiplo de cadeia antiga / nova é gerenciado em um mapa de hash.

O diretório pode ser definido via linha de comando ou variável de ambiente, o mapa é codificado no script, mas você pode modificar o código para carregar de um arquivo, se necessário.

Requer bash 4.2, devido a algum novo recurso.

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
fonte
1

Usar o comando ack seria muito mais rápido assim:

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

Além disso, se você tiver um espaço em branco no resultado da pesquisa. Você precisa escapar disso.

Patoshi パ ト シ
fonte
0

Estou dando um exemplo para corrigir um erro shebang comum em fontes python.

Você pode tentar a abordagem grep / sed. Aqui está um que funciona com o GNU sed e não quebra um repositório git:

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

Ou você pode usar greptile :)

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

Acabei de testar o primeiro script e o segundo também deve funcionar. Tenha cuidado com os caracteres de escape, acho que deve ser mais fácil usar o greptile na maioria dos casos. Obviamente, você pode fazer muitas coisas interessantes com o sed e, para isso, pode ser preferível dominar o uso com o xargs.

exa
fonte
0

Encontrei este em outro post (não me lembro qual) e, embora não seja o mais elegante, é simples e, como um usuário iniciante do Linux, não me deu problemas

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

se você tiver espaços ou outros caracteres especiais, use a \

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

para cadeias de caracteres nos subdiretórios use **

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

O comando abaixo pode ser usado para pesquisar primeiro os arquivos e substituí-los:

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

Por exemplo

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

Há uma maneira mais fácil usando um arquivo de script simples:

   # sudo chmod +x /bin/replace_string_files_present_dir

abra o arquivo no gedit ou em um editor de sua escolha, eu uso o gedit aqui.

   # sudo gedit /bin/replace_string_files_present_dir

Em seguida, no editor, cole o seguinte no arquivo

   #!/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.

Salve o arquivo, feche o gedit , saia do terminal ou simplesmente feche-o e inicie-o para poder carregar o novo script que você adicionou.

Navegue para o diretório em que você tem vários arquivos que deseja editar. Então corra:

  #replace_string_files_present_dir

Pressione enter e isso substituirá automaticamente a cadeia antiga e a cadeia antiga1 em todos os arquivos que as contêm pela nova cadeia e nova cadeia1 respectivamente.

Irá pular todos os diretórios e arquivos que não contêm as strings antigas.

Isso pode ajudar a eliminar o trabalho tedioso de digitar, caso você tenha vários diretórios com arquivos que precisam ser substituídos. Tudo o que você precisa fazer é navegar para cada um desses diretórios e executar:

#replace_string_files_present_dir

Tudo o que você precisa fazer é garantir que você incluiu ou adicionou todas as seqüências de substituição, como mostrei acima:

replace "oldstring" "newstring" -- *

no final do arquivo / bin / replace_string_files_present_dir .

Para adicionar uma nova string de substituição, basta abrir o script que criamos, digitando o seguinte no terminal:

sudo gedit /bin/replace_string_files_present_dir

Não se preocupe com o número de strings de substituição adicionadas, elas não terão efeito se a string antiga não for encontrada.

briancollins081
fonte
Geralmente, quando as pessoas perguntam "como $ {seja o que for}" no bash, estão pedindo uma versão compacta para incluir em um script ou instrução de trabalhos de IC (digamos, a .gitlab-ci.ymlou a travis.yml). Cada turno que consiste em escrever um script para executá-lo mais tarde é um anti-padrão, pois você precisará criar um script para a criação do script (e eu fico confuso, na maioria das vezes)
zar3bski
-4

$ substitua "De" "Para" - `find / path / to / folder -type f`

(replace - um utilitário de substituição de string do MySQL Database System)

bashconsole
fonte