Localizar e substituir dentro de um arquivo de texto a partir de um comando Bash

561

Qual é a maneira mais simples de encontrar e substituir uma determinada string de entrada, digamos abc, e substituir por outra string, digamos XYZno arquivo /tmp/file.txt?

Estou escrevendo um aplicativo e usando o IronPython para executar comandos através do SSH - mas não conheço o Unix tão bem e não sei o que procurar.

Ouvi dizer que o Bash, além de ser uma interface de linha de comando, pode ser uma linguagem de script muito poderosa. Portanto, se isso for verdade, presumo que você possa executar ações como estas.

Posso fazer isso com o bash, e qual é o script mais simples (uma linha) para alcançar meu objetivo?

Cinza
fonte

Respostas:

930

A maneira mais fácil é usar sed (ou perl):

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

O que invocará o sed para fazer uma edição no local devido à -iopção Isso pode ser chamado a partir do bash.

Se você realmente deseja usar apenas o bash, o seguinte pode funcionar:

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

Isso faz um loop em cada linha, substituindo e gravando em um arquivo temporário (não quero prejudicar a entrada). A mudança no final apenas muda temporariamente para o nome original.

johnny
fonte
3
Exceto que invocar mv é quase tão 'não Bash' quanto usar sed. Eu quase disse o mesmo de eco, mas é uma concha embutida.
fino
5
O argumento -i para sed não existe no Solaris (e eu pensaria em outras implementações), portanto, lembre-se disso. Apenas passou vários minutos tentando descobrir isso ...
Panky
2
Nota para self: sobre a expressão regular de sed: s/..../..../ - Substitutee /g - Global
soma de verificação
89
Nota para usuários de Mac que recebem um invalid command code Cerro ... Para substituições no local, o BSD sedrequer uma extensão de arquivo após o -isinalizador, pois salva um arquivo de backup com a extensão especificada. Por exemplo: sed -i '.bak' 's/find/replace/' /file.txt Você pode pular o backup usando uma string vazia assim:sed -i '' 's/find/replace/' /file.txt
Austin
8
Dica: Se você quiser caso de uso repalce insensívels/abc/XYZ/gi
Boris D. Teoharov
166

A manipulação de arquivos normalmente não é feita pelo Bash, mas por programas invocados pelo Bash, por exemplo:

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

A -ibandeira diz para fazer uma substituição no local.

Veja man perlrunpara obter mais detalhes, incluindo como fazer um backup do arquivo original.

Alnitak
fonte
37
O purista em mim diz que você não pode ter certeza de que o Perl estará disponível no sistema. Mas esse raramente é o caso hoje em dia. Talvez eu esteja mostrando minha idade.
fino
3
Você pode mostrar um exemplo mais complexo. Algo como substituir "chdir / blah" por "chdir / blah2". Eu tentei perl -pi -e 's/chdir (?:\\/[\\w\\.\\-]+)+/chdir blah/g' text, mas continuo recebendo um erro com Não ter espaço entre o padrão e a palavra a seguir é preterido na linha -e 1. Incomparável (no regex; marcado por <- HERE em m / (chdir) () (<- HERE ?: \\ / na linha -e 1.
CMCDragonkai
@CMCDragonkai Verifique esta resposta: stackoverflow.com/a/12061491/2730528
Alfonso Santiago
69

Fiquei surpreso quando me deparei com isso ...

Há um replacecomando que acompanha o "mysql-server"pacote; portanto, se você o instalou, tente:

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

Veja man replacemais sobre isso.

rayro
fonte
12
Duas coisas são possíveis aqui: a) replaceé uma ferramenta independente útil e o pessoal do MySQL deve liberá-la separadamente e depender dela b) replacerequer um pouco de MySQL o_O De qualquer maneira, instalar o mysql-server para obter substituição seria a coisa errada a fazer :)
Philip Whitehouse
só funciona para mac? no meu ubuntu I CentOS que comando não existe
paul
1
Isso porque você não possui o mysql-serverpacote instalado. Como apontado por @rayro, replacefaz parte disso.
Phius
2
"Aviso: a substituição está obsoleta e será removida em uma versão futura."
Steven Vachon
1
Cuidado para não executar o comando REPLACE no Windows! No Windows, o comando REPLACE é para uma replicação rápida de arquivos. Não é relevante para esta discussão.
Maor
53

Este é um post antigo, mas para quem quiser usar variáveis ​​como @centurian, as aspas simples significam que nada será expandido.

Uma maneira simples de obter variáveis ​​é fazer a concatenação de cadeias, pois isso é feito por justaposição no bash, o seguinte deve funcionar:

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

O Bash, como outras conchas, é apenas uma ferramenta para coordenar outros comandos. Normalmente, você tentaria usar comandos UNIX padrão, mas é claro que pode usar o Bash para chamar qualquer coisa, incluindo seus próprios programas compilados, outros scripts de shell, scripts Python e Perl etc.

Nesse caso, existem algumas maneiras de fazer isso.

Se você deseja ler um arquivo e gravá-lo em outro arquivo, pesquisando / substituindo à medida que avança, use sed:

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

Se você deseja editar o arquivo no local (como se estivesse abrindo o arquivo em um editor, editando-o e salvando-o), forneça instruções ao editor de linha 'ex'

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

Ex é como vi sem o modo de tela cheia. Você pode dar os mesmos comandos que você faria no prompt ':' do vi.

fino
fonte
33

Encontrei esse tópico entre outros e concordo que ele contém as respostas mais completas, por isso estou adicionando as minhas também:

  1. sede edsão tão úteis ... à mão. Veja este código de @Johnny:

    sed -i -e 's/abc/XYZ/g' /tmp/file.txt
  2. Quando minha restrição é usá-lo em um script de shell, nenhuma variável pode ser usada no lugar de "abc" ou "XYZ". O BashFAQ parece concordar com o que eu entendo pelo menos. Então, eu não posso 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

    Mas o que nós podemos fazer? Como, @Johnny disse, use um while read...mas, infelizmente, esse não é o fim da história. O seguinte funcionou bem comigo:

    #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 pode ver se nullglobestá definido então, ele se comporta estranhamente quando há uma string que contém a *as in:

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

    que se torna

    <VirtualHost ServerName www.example.com

    não há colchetes angulares finais e o Apache2 nem pode carregar.

  4. Esse tipo de análise deve ser mais lento que a pesquisa com um clique e substituir, mas, como você já viu, existem quatro variáveis ​​para quatro padrões de pesquisa diferentes trabalhando em um ciclo de análise.

A solução mais adequada em que consigo pensar com as suposições dadas do problema.

centuriano
fonte
12
No seu (2) - você pode fazer sed -e "s/$x/$y/", e vai funcionar. Não são aspas duplas. Pode ficar seriamente confuso se as seqüências de caracteres nas próprias variáveis ​​contiverem caracteres com significado especial. Por exemplo, se x = "/" ou x = "\". Quando você enfrenta esses problemas, provavelmente significa que você deve parar de tentar usar o shell para este trabalho.
magro
Oi esbelto, vejo que você também é contra o uso de Perl. Qual é a sua solução? Porque, de fato, quero alterar dinamicamente um caminho em um arquivo, o que significa que tenho muito / na string!
Mahdi
20

Você pode usar sed:

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

Use -ipara "ignorar maiúsculas e minúsculas" se não tiver certeza de que o texto a ser encontrado é abcou ABCou AbC, etc.

Você pode usar finde, sedse não souber seu nome de arquivo:

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

Encontre e substitua em todos os arquivos Python:

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

Você também pode usar o edcomando para pesquisar em arquivos e substituir:

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

Veja mais em " Editando arquivos via scripts comed ".

o homem de lata
fonte
3
Esta solução é independente das incompatibilidades do GNU / FreeBSD (Mac OSX) (ao contrário sed -i <pattern> <filename>). Muito agradável!
Peterino 12/09
11

Se o arquivo em que você está trabalhando não é tão grande, e armazená-lo temporariamente em uma variável não é problema, você pode usar a substituição de string Bash em todo o arquivo de uma só vez - não há necessidade de revisá-lo linha por linha:

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

Todo o conteúdo do arquivo será tratado como uma cadeia longa, incluindo quebras de linha.

XYZ pode ser uma variável $replacement, por exemplo , e uma vantagem de não usar sed aqui é que você não precisa se preocupar que a cadeia de pesquisa ou substituição possa conter o caractere delimitador do padrão sed (geralmente, mas não necessariamente, /). Uma desvantagem é não poder usar expressões regulares ou nenhuma das operações mais sofisticadas do sed.

johnraff
fonte
Alguma dica para usar isso com caracteres de tabulação? Por alguma razão, meu script não encontra nada com guias após mudar de sed com muito escape para esse método.
Brian Hannay
2
Se você quiser colocar uma guia na string que está substituindo, poderá fazê-lo com a sintaxe "aspas simples em dólar" do Bash, para que uma guia seja representada por $ '\ t' e você possa fazer $ echo 'tab' $ '\ t''separated'> testfile; $ file_contents = $ (<arquivo de teste); $ echo "$ {file_contents // $ '\ t' / TAB}"; tabTABseparated `
johnraff 27/10
5

Experimente o seguinte comando shell:

find ./  -type f -name "file*.txt" | xargs sed -i -e 's/abc/xyz/g'
J Ajay
fonte
4
Essa é uma excelente resposta para "como também acidentalmente todos os arquivos em todos os subdiretórios", mas isso não parece ser o que é solicitado aqui.
tripleee
Essa sintaxe não funcionará para a versão BSD de sed, use em sed -i''vez disso.
kenorb 26/09/19
5

Para editar o texto no arquivo de maneira não interativa, você precisa de um editor de texto no local, como o vim.

Aqui está um exemplo simples de como usá-lo na linha de comando:

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

Isso é equivalente à resposta @slim do ex editor, que é basicamente a mesma coisa.

Aqui estão alguns exexemplos práticos.

Substituindo texto foopor barno arquivo:

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

Removendo os espaços em branco à direita para vários arquivos:

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

Solução de problemas (quando o terminal está preso):

  • Adicione -V1param para mostrar mensagens detalhadas.
  • Forçar Encerrar por: -cwq!.

Veja também:

kenorb
fonte
Queria fazer as substituições interativamente. Portanto, tentei "vim -esnc '% s / foo / bar / gc |: wq' file.txt". Mas o terminal está preso agora. Como devemos fazer as substituições interativamente sem que o shell bash se comporte de maneira estranha.
precisa saber é o seguinte
Para depurar, adicione -V1, para forçar o encerramento, use wq!.
kenorb
2

Você também pode usar python no script bash. Não tive muito sucesso com algumas das principais respostas aqui e achei que funcionava sem a necessidade de loops:

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

Você pode usar o comando rpl. Por exemplo, você deseja alterar o nome de domínio em todo o projeto php.

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

Esta não é uma clara causa de causa, mas é muito rápida e útil. :)

zalex
fonte