Como fazer uma busca / substituição recursiva de uma string por awk ou sed?


674

Como localizo e substituo cada ocorrência de:

subdomainA.example.com

com

subdomainB.example.com

em cada arquivo de texto sob a /home/www/árvore de diretórios recursivamente?


93
Dica: Não faça o abaixo em uma árvore de check-out svn ... ele substituirá os arquivos mágicos da pasta .svn.
J. Polfer

7
Oh meu Deus, isso é exatamente o que eu acabei de fazer. Mas funcionou e não parece ter causado nenhum mal. Qual é o pior que poderia acontecer?
23413 Katzwinkel

5
@ J.Katzwinkel: no mínimo, pode danificar as somas de verificação, o que pode danificar seu repositório.
Ninjagecko 14/05

3
Dica rápida para todas as pessoas que usam o sed: ele adicionará novas linhas finais aos seus arquivos. Se você não os quiser, primeiro faça uma busca-substituição que não corresponda a nada e comprometa-a com o git. Então faça o real. Em seguida, faça uma nova recuperação interativa e exclua a primeira.
funroll

5
Você pode excluir um diretório, como git, a partir dos resultados usando -path ./.git -prune -oem find . -path ./.git -prune -o -type f -name '*matchThisText*' -print0antes de tubulação para xargs
devinbost

Respostas:


849
find /home/www \( -type d -name .git -prune \) -o -type f -print0 | xargs -0 sed -i 's/subdomainA\.example\.com/subdomainB.example.com/g'

-print0diz findpara imprimir cada um dos resultados separados por um caractere nulo, em vez de uma nova linha. No caso improvável de o seu diretório ter arquivos com novas linhas nos nomes, isso ainda permite xargstrabalhar nos nomes de arquivos corretos.

\( -type d -name .git -prune \)é uma expressão que pula completamente todos os diretórios nomeados .git. Você pode expandi-lo facilmente, se você usa o SVN ou tem outras pastas que deseja preservar - basta comparar com mais nomes. É aproximadamente equivalente a -not -path .git, mas mais eficiente, porque, em vez de verificar todos os arquivos no diretório, ignora-o completamente. O -odepois é necessário por causa de como -prunerealmente funciona.

Para mais informações, consulte man find.


132
No OSX, você pode encontrar um sed: 1: "...": invalid command code .problema. Parece que a opção -i espera extensão e analisa o 's/../...'comando. Solução: passe a extensão '' para a opção -i sed -i '' 's/....
Robert Lujo

6
Nota: se você usar isso em um diretório e se perguntar por que svn stnão mostra alterações, é porque você modificou os arquivos nos diretórios .svn também! Use em find . -maxdepth 1 -type f -print0 | xargs -0 sed -i 's/toreplace/replaced/g'vez disso.
ACK_stoverflow 26/09/13

57
Além disso, tenha cuidado se você estiver em um repositório Git. Eu pensei que era inteligente testando isso em uma ramificação clara para que eu pudesse reverter se fizesse algo ruim, mas corrompeu meu índice git.
Ciryon 4/10

13
Use isso grep -r 'hello' -l --null . | xargs -0 sed -i 's#hello#world#g'para evitar a edição de arquivos não relacionados (o sed pode alterar a codificação do arquivo).
caiguanhao

6
"mas corrompeu meu índice git." Não se preocupe muito com isso, você pode apenas fazer find .git ... | ... 'sed -i s/(the opposite from before)/g'para corrigir o seu índice git
Massey101

259

Nota : Não execute este comando em uma pasta incluindo um repositório git - alterações em .git podem corromper seu índice git.

find /home/www/ -type f -exec \
    sed -i 's/subdomainA\.example\.com/subdomainB.example.com/g' {} +

Comparado a outras respostas aqui, isso é mais simples do que a maioria e usa sed em vez de perl, que é o que a pergunta original fazia.


50
Observe que, se você estiver usando o BSD sed (incluindo no Mac OS X), precisará fornecer uma sequência de argumentos vazia e explícita arg à -iopção do sed . ou seja: sed -i '' 's/original/replacement/g'
Nathan Craike 23/03

2
@JohnZwinck Meu erro, perdeu o +. Estranhamente, a solução da Nikita é mais rápida para mim.
Sam

6
@AoeAoe: Isso +reduz bastante o número de sedprocessos gerados. É mais eficiente.
John Zwinck

4
Como posso fazer isso com segurança em uma pasta com um repositório git?
24516 Hatshepsut

20
É seguro para executar em uma pasta que contém um repositório git se você excluir a repo de seus resultados da procura: find . -not -path '*/\.git*' -type f ....
Dale Anderson

210

A maneira mais simples para mim é

grep -rl oldtext . | xargs sed -i 's/oldtext/newtext/g'

1
@ Anatoly: apenas uma pergunta: como posso excluir arquivos binários (arquivos executáveis) ?
user2284570

3
@ user2284570 Use os sinalizadores -Iou --binary-file=without-matchgrep.
Zéychin 15/09/14

34
Isso funciona especialmente bem quando você precisa excluir diretórios, como com .svn. Por exemplo:grep -rl oldtext . --exclude-dir=.svn | xargs sed -i 's/oldtext/newtext/g'
phyatt 13/11/2015

11
brew install gnu-sede use gsedno OSX para evitar um mundo de dor.
1

1
caras por favor, atenção, se seu projeto é git versão, use este em vez disso: git grep -rl oldtext . | xargs sed -i 's/oldtext/newtext/g'. não é nada legal f * ck up seu .gitdir
Paolo

61

Todos os truques são quase os mesmos, mas eu gosto deste:

find <mydir> -type f -exec sed -i 's/<string1>/<string2>/g' {} +
  • find <mydir>: procure no diretório

  • -type f:

    O arquivo é do tipo: arquivo regular

  • -exec command {} +:

    Essa variante da ação -exec executa o comando especificado nos arquivos selecionados, mas a linha de comandos é criada anexando cada nome de arquivo selecionado no final; o número total de invocações do comando será muito menor que o número de arquivos correspondentes. A linha de comando é construída da mesma maneira que o xargs cria suas linhas de comando. Somente uma instância de `{} 'é permitida dentro do comando. O comando é executado no diretório inicial.


@ user2284570 com -exec? Tente definir o caminho para executável em vez de um nome de ferramenta.
I159 04/08/19

@ I159: Não: exclua binários executáveis (mas inclua scripts de shell) .
user2284570

8
@ I159 Esta resposta não é idêntica à de John Zwinck ?
Reinstate Monica Please

1
@ user2284570 O conceito de "arquivo binário" não está totalmente bem definido. Você pode usar o filecomando para tentar determinar o tipo de cada arquivo, mas as variações aleatórias em sua saída podem ser um pouco desconcertantes. A opção -I(aka --mime) ajuda um pouco, ou --mime-typese você tiver. O modo exato de refatorar essa linha única pura para fazer isso está lamentavelmente fora do escopo desta pequena caixa de comentários. Talvez poste uma pergunta separada se precisar de ajuda? (Talvez adicione um comentário com um link para ele aqui, então.) #
Tripleee 18/02/16

1
a resposta mais limpa! Obrigado mate
jukerok

39
cd /home/www && find . -type f -print0 |
  xargs -0 perl -i.bak -pe 's/subdomainA\.example\.com/subdomainB.example.com/g'

2
Estou curioso, existe uma razão para usar -print0e em xargsvez de -execou -execdir?
1840 Philipp

4
Existe: from "man find": o comando especificado é executado uma vez para cada arquivo correspondido. Ou seja, se houver 2000 arquivos em / home / www, 'find ... -exec ...' resultará em 2000 invocações de perl; Considerando que 'encontrar ... | xargs ... 'invocará o perl apenas uma ou duas vezes (assumindo ARG_MAX com cerca de 32 K e tamanho médio de nome de arquivo 20).
Empregado Russian

2
@ Russo empregado: é por isso que você usaria find -exec command {} +- evita invocações excessivas do comando como xargs, mas sem o processo separado.
21926 John Zwinck

2
Em qual plataforma? A solução xargs é portátil, as invocações "mágicas" de "find ... -exec" que não invocam um subprocesso para cada arquivo encontrado.
Empregado Russian

4
@EmployedRussian, find -exec ... {} +é especificado no POSIX desde 2006.
Charles Duffy

34

Para mim, a solução mais fácil de lembrar é https://stackoverflow.com/a/2113224/565525 , ou seja:

sed -i '' -e 's/subdomainA/subdomainB/g' $(find /home/www/ -type f)

NOTA : -i ''resolve o problema OSXsed: 1: "...": invalid command code .

NOTA : Se houver muitos arquivos para processar, você receberá Argument list too long. A solução alternativa - uso find -execou xargssolução descrita acima.


4
A workarounddeve ser a sintaxe preferida em todos os casos.
Reinstate Monica Please

1
O problema com a substituição de comando $(find...)é que não há como o shell manipular nomes de arquivos com espaços em branco ou outros metacaracteres do shell. Se você sabe que isso não é um problema, essa abordagem é adequada; mas temos muitas perguntas em que as pessoas não foram avisadas sobre esse problema ou não entenderam o aviso.
Tripleee 13/03

30

Para quem usa o silver searcher ( ag)

ag SearchString -l0 | xargs -0 sed -i 's/SearchString/Replacement/g'

Como o ag ignora as pastas / arquivos git / hg / svn por padrão, é seguro executar dentro de um repositório.


16

Um oneliner agradável como um extra. Usando git grep.

git grep -lz 'subdomainA.example.com' | xargs -0 perl -i'' -pE "s/subdomainA.example.com/subdomainB.example.com/g"

3
É uma boa idéia se você trabalha em um repositório git, pois não corre o risco de sobrescrever .git / contents (conforme relatado nos comentários para outra resposta).
mahemoff

1
Obrigado, eu uso-o como uma função bash refactor() { echo "Replacing $1 by $2 in all files in this git repository." git grep -lz $1| xargs -0 perl -i'' -pE "s/$1/$2/g" }Uso, por exemplo, para substituir 'word' por 'sword': refactor word sworddepois verifique o que ele fez git diff.
Paul Rougieux 18/12/19

16

Para reduzir os arquivos para recursivamente sed, você pode, greppara sua instância de string:

grep -rl <oldstring> /path/to/folder | xargs sed -i s^<oldstring>^<newstring>^g

Se você executar, man grepnotará que também pode definir um--exlude-dir="*.git" sinalizador se desejar omitir a pesquisa nos diretórios .git, evitando problemas de índice git, como outros indicaram educadamente.

Levando você a:

grep -rl --exclude-dir="*.git" <oldstring> /path/to/folder | xargs sed -i s^<oldstring>^<newstring>^g

13

Este é compatível com os repositórios git e um pouco mais simples:

Linux:

git grep -l 'original_text' | xargs sed -i 's/original_text/new_text/g'

Mac:

git grep -l 'original_text' | xargs sed -i '' -e 's/original_text/new_text/g'

(Obrigado a http://blog.jasonmeridth.com/posts/use-git-grep-to-replace-strings-in-files-in-your-git-repository/ )


Mais sensato usar git-grepa -zopção em conjunto com xargs -0.
gniourf_gniourf

git grepobviamente, só faz sentido em um gitrepositório. A substituição geral seria grep -r.
tripleee

@gniourf_gniourf Você pode explicar?
precisa saber é o seguinte

2
@PetrPeller: with -z, git-grepseparará os campos de saída por bytes nulos em vez de novas linhas; e com -0, xargslerá a entrada separada por bytes nulos, em vez de espaços em branco (e não fará coisas estranhas com aspas). Então, se você não quer o comando de pausa, se os nomes de arquivos contêm espaços, aspas ou outros personagens engraçados, o comando é: git grep -z -l 'original_text' | xargs -0 sed ....
precisa saber é o seguinte

10
find /home/www/ -type f -exec perl -i.bak -pe 's/subdomainA\.example\.com/subdomainB.example.com/g' {} +

find /home/www/ -type f listará todos os arquivos em / home / www / (e seus subdiretórios). O sinalizador "-exec" indica ao find para executar o seguinte comando em cada arquivo encontrado.

perl -i.bak -pe 's/subdomainA\.example\.com/subdomainB.example.com/g' {} +

é o comando executado nos arquivos (muitos de cada vez). O {}é substituído por nomes de arquivos. O +no final do comando diz findpara criar um comando para muitos nomes de arquivos.

De acordo com a findpágina do manual: "A linha de comando é construída da mesma maneira que o xargs cria suas linhas de comando."

Assim, é possível atingir seu objetivo (e manipular nomes de arquivos contendo espaços) sem usar xargs -0, ou -print0.


8

Eu só precisava disso e não estava satisfeito com a velocidade dos exemplos disponíveis. Então eu criei o meu próprio:

cd /var/www && ack-grep -l --print0 subdomainA.example.com | xargs -0 perl -i.bak -pe 's/subdomainA\.example\.com/subdomainB.example.com/g'

O Ack-grep é muito eficiente para encontrar arquivos relevantes. Esse comando substituiu ~ 145.000 arquivos com facilidade, enquanto outros demoraram tanto que eu mal podia esperar até que eles terminassem.


Bom, mas grep -ril 'subdomainA' *não é nem de longe tão rápido quanto grep -Hr 'subdomainA' * | cut -d: -f1.
trusktr

@ Henno: apenas uma pergunta: como posso excluir arquivos binários (arquivos executáveis) ?
user2284570

O ack-grep faz isso automaticamente para você.
Henno 4/08/14

@ Henno: Inclui scripts de shell?
user2284570

Sim. Aqui está uma lista completa dos tipos de arquivos suportados: beyondgrep.com/documentation
Henno

6

Um método direto, se você precisar excluir diretórios ( --exclude-dir=.svn) e também pode ter nomes de arquivos com espaços (usando 0Byte com grep -Zexargs -0

grep -rlZ oldtext . --exclude-dir=.svn | xargs -0 sed -i 's/oldtext/newtext/g'

5

grep -lr 'subdomainA.example.com' | while read file; do sed -i "s/subdomainA.example.com/subdomainB.example.com/g" "$file"; done

Eu acho que a maioria das pessoas não sabe que pode inserir algo em um "arquivo de leitura ao mesmo tempo" e evita esses argumentos desagradáveis ​​-print0, enquanto preserva espaços nos nomes de arquivos.

A adição adicional de um echobefore antes do sed permite que você veja quais arquivos serão alterados antes de realmente fazê-lo.


O motivo -print0é útil: ele lida com casos que while readsimplesmente não conseguem lidar - uma nova linha é um caractere válido em um nome de arquivo Unix; portanto, para que seu código seja completamente robusto, ele também precisa lidar com esses nomes de arquivo. (Além disso, você quer read -revitar algumas traquinas POSIX comportamento legado no read.)
tripleee

Além disso, o sedé um não-op se não houver correspondências, portanto grepnão é realmente necessário; embora seja uma otimização útil para evitar a reescrita de arquivos que não contêm correspondências, se você tiver muitas delas ou desejar evitar a atualização desnecessária de carimbos de data nos arquivos.
Tripleee

5

Você pode usar o awk para resolver isso como abaixo,

for file in `find /home/www -type f`
do
   awk '{gsub(/subdomainA.example.com/,"subdomainB.example.com"); print $0;}' $file > ./tempFile && mv ./tempFile $file;
done

espero que isso ajude você !!!


Funciona em MacOs sem problemas! Todos os sedcomandos baseados falharam quando os binários foram incluídos, mesmo com as configurações específicas do osx.
Jankapunkt

Cuidado ... isso explodirá se algum dos arquivos findretornados tiver um espaço em seus nomes! É muito mais seguro usar while read: stackoverflow.com/a/9612560/1938956 #
Soren Bjornstad 15/02/19

5

Maneira mais simples de substituir ( todos os arquivos, diretório, recursivo )

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

Nota: Às vezes você pode precisar ignorar alguns arquivos ocultos, ou seja,.git , , pode usar o comando acima.

Se você deseja incluir arquivos ocultos, use

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

Nos dois casos, a string fooserá substituída por uma nova stringbar


4

Tente o seguinte:

sed -i 's/subdomainA/subdomainB/g' `grep -ril 'subdomainA' *`

1
Oi @RikHic, boa dica - estava pensando em algo assim; Infelizmente, a formatação acima não deu certo :) Então, eu vou tentar com uma pré tag (não funciona) - então, com backticks de escape: sed -i 's/subdomainA/subdomainB/g'` grep -ril 'subdomainA' /home/www/*` - isso ainda não parece muito bom, mas deve sobreviver copypaste :) Saúde!
Sdaau

4
#!/usr/local/bin/bash -x

find * /home/www -type f | while read files
do

sedtest=$(sed -n '/^/,/$/p' "${files}" | sed -n '/subdomainA/p')

    if [ "${sedtest}" ]
    then
    sed s'/subdomainA/subdomainB/'g "${files}" > "${files}".tmp
    mv "${files}".tmp "${files}"
    fi

done

4

De acordo com este post do blog:

find . -type f | xargs perl -pi -e 's/oldtext/newtext/g;'

Como você escapa de barras /? Por exemplo, eu quero substituir endereços IP: xxx.xxx.xxx.xxxforxxx.xxx.xxx.xxx/folder
Pathros

Você pode escapar do /com \. Por exemplo:find . -type f | xargs perl -pi -e 's/xxx.xxx.xxx.xxx\/folder/newtext/g;'
J.Hpour

3

Se você não se importa de usar vimjunto com grepou findferramentas, pode acompanhar a resposta dada pelo usuário Gert neste link -> Como fazer uma substituição de texto em uma hierarquia de pastas grandes? .

Aqui está o acordo:

  • grep recursivamente para a sequência que você deseja substituir em um determinado caminho e utilize apenas o caminho completo do arquivo correspondente. (esse seria o $(grep 'string' 'pathname' -Rl).

  • (opcional) se você quiser fazer um pré-backup desses arquivos no diretório centralizado, talvez também possa usá-lo: cp -iv $(grep 'string' 'pathname' -Rl) 'centralized-directory-pathname'

  • depois disso, você pode editar / substituir à vontade, vimseguindo um esquema semelhante ao fornecido no link fornecido:

    • :bufdo %s#string#replacement#gc | update

2

Um pouco da velha escola, mas isso funcionou no OS X.

Existem alguns truques:

• Editará apenas arquivos com extensão .slsno diretório atual

.deve ser escapado para garantir sedque não os avalie como "qualquer caractere"

,é usado como seddelimitador em vez do usual/

Observe também que isso é para editar um modelo Jinja para passar um variableno caminho de um import(mas isso não é tópico).

Primeiro, verifique se o seu comando sed faz o que você deseja (isso imprimirá apenas as alterações no stdout, não os arquivos):

for file in $(find . -name *.sls -type f); do echo -e "\n$file: "; sed 's,foo\.bar,foo/bar/\"+baz+\"/,g' $file; done

Edite o comando sed conforme necessário, quando estiver pronto para fazer alterações:

for file in $(find . -name *.sls -type f); do echo -e "\n$file: "; sed -i '' 's,foo\.bar,foo/bar/\"+baz+\"/,g' $file; done

Observe o comando -i ''in sed , eu não queria criar um backup dos arquivos originais (como explicado em Edições no local com sed no OS X ou no comentário de Robert Lujo nesta página).

Gente sedutora feliz!


2

só para evitar mudar também

  • NearlysubdomainA.example.com
  • subdomínioA.exemplo.comp.outros

mas ainda

  • subdomínioA.exemplo.com.IsIt.good

(talvez não seja bom na idéia por trás da raiz do domínio)

find /home/www/ -type f -exec sed -i 's/\bsubdomainA\.example\.com\b/\1subdomainB.example.com\2/g' {} \;

2

Eu apenas uso tops:

find . -name '*.[c|cc|cp|cpp|m|mm|h]' -print0 |  xargs -0 tops -verbose  replace "verify_noerr(<b args>)" with "__Verify_noErr(<args>)" \
replace "check(<b args>)" with "__Check(<args>)" 

mais um para `` *. [c | cc | cp | cpp | m | mm | h] ''
FractalSpace

2

Aqui está uma versão que deve ser mais geral que a maioria; não requer find(usando em duvez disso), por exemplo. Requer xargs, que é encontrado apenas em algumas versões do Plano 9 (como o 9front).

 du -a | awk -F' '  '{ print $2 }' | xargs sed -i -e 's/subdomainA\.example\.com/subdomainB.example.com/g'

Se você deseja adicionar filtros como extensões de arquivo, use grep:

 du -a | grep "\.scala$" | awk -F' '  '{ print $2 }' | xargs sed -i -e 's/subdomainA\.example\.com/subdomainB.example.com/g'

1

Para Qshell (qsh) no IBMi, não faça o bash conforme marcado pelo OP.

Limitações dos comandos qsh:

  • find não tem a opção -print0
  • xargs não possui a opção -0
  • sed não tem a opção -i

Assim, a solução em qsh:

    PATH='your/path/here'
    SEARCH=\'subdomainA.example.com\'
    REPLACE=\'subdomainB.example.com\'

    for file in $( find ${PATH} -P -type f ); do

            TEMP_FILE=${file}.${RANDOM}.temp_file

            if [ ! -e ${TEMP_FILE} ]; then
                    touch -C 819 ${TEMP_FILE}

                    sed -e 's/'$SEARCH'/'$REPLACE'/g' \
                    < ${file} > ${TEMP_FILE}

                    mv ${TEMP_FILE} ${file}
            fi
    done

Ressalvas:

  • A solução exclui o tratamento de erros
  • Not Bash como marcado por OP

Isso tem alguns problemas incômodos com citações e também com linhas de leitura for.
Tripleee

1

Se você quiser usar isso sem destruir completamente o seu repositório SVN, poderá dizer a 'find' para ignorar todos os arquivos ocultos, fazendo:

find . \( ! -regex '.*/\..*' \) -type f -print0 | xargs -0 sed -i 's/subdomainA.example.com/subdomainB.example.com/g'

Os parênteses parecem supérfluos. Isso anteriormente tinha um erro de formatação que o tornava inutilizável (a renderização do Markdown consumia alguns caracteres do regex).
Tripleee

1

Usando a combinação de grepesed

for pp in $(grep -Rl looking_for_string)
do
    sed -i 's/looking_for_string/something_other/g' "${pp}"
done

Tripleee @ Eu modifiquei isso um pouco. Nesse caso, a saída para a grep -Rl patternlista gerada por comando de arquivos em que o padrão está. Os arquivos não são lidos em forloop.
Pawel

Hã? Você ainda tem um forloop; se algum nome de arquivo retornado contiver espaço em branco, ele não funcionará corretamente, porque o shell simboliza a forlista de argumentos. Mas então você usa a variável de nome de arquivo sem aspas dentro do loop, portanto ela seria quebrada lá se você corrigisse isso. A correção desses erros restantes tornaria o seu idêntico à resposta do @ MadMan2064.
Tripleee

@ tripleeee sim, isso é verdade, eu perdi isso.
Pawel

1

Para substituir todas as ocorrências em um repositório git, você pode usar:

git ls-files -z | xargs -0 sed -i 's/subdomainA\.example\.com/subdomainB.example.com/g'

Consulte Listar arquivos no repositório git local? para outras opções para listar todos os arquivos em um repositório. As -zopções dizem ao git para separar os nomes dos arquivos com um byte zero, o que garante que xargs(com a opção -0) possam separar os nomes de arquivos, mesmo que contenham espaços ou outros enfeites.


1
perl -p -i -e 's/oldthing/new_thingy/g' `grep -ril oldthing *`

1
Não usando awk/ sed, mas o perl é comum (exceto sistemas / embarcados apenas com o busybox).
pevik

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.