Localizar e substituir no arquivo e substituí-lo não funciona, esvazia o arquivo

604

Gostaria de executar uma busca e substituição em um arquivo HTML através da linha de comando.

Meu comando é mais ou menos assim:

sed -e s/STRING_TO_REPLACE/STRING_TO_REPLACE_IT/g index.html > index.html

Quando eu executo isso e olho o arquivo depois, ele está vazio. Excluiu o conteúdo do meu arquivo.

Quando eu executo isso depois de restaurar o arquivo novamente:

sed -e s/STRING_TO_REPLACE/STRING_TO_REPLACE_IT/g index.html

O stdouté o conteúdo do arquivo e a localização e substituição foram executadas.

Por que isso está acontecendo?

BBales
fonte
13
Alternativa Perl:perl -pi -w -e 's/STRING_TO_REPLACE/REPLACE_WITH/g;' index.html
Gjorgji Tashkovski
muito relacionado sedcomando para encontrar uma string e substituir a linha inteira: stackoverflow.com/questions/11245144/...
cregox

Respostas:

917

Quando o shell> index.htmlna linha de comando, ele abre o arquivo index.htmlpara gravação , limpando todo o conteúdo anterior.

Para corrigir isso, você precisa passar a -iopção de sedfazer as alterações embutidas e criar um backup do arquivo original antes que ele faça as alterações no local:

sed -i.bak s/STRING_TO_REPLACE/STRING_TO_REPLACE_IT/g index.html

Sem o .bak, o comando falhará em algumas plataformas, como o Mac OSX.

codaddict
fonte
20
Dizer em truncates the filevez de opens the fileprovavelmente torna mais claro.
Mikel
12
Pelo menos no meu mac, a primeira sugestão não funciona ... se você estiver fazendo uma substituição no local de um arquivo, precisará especificar uma extensão. Você pode, pelo menos, passar em uma extensão de comprimento zero: sed -i / STRING_TO_REPLACE / STRING_TO_REPLACE_IT / g index.html
Tom Lianza
5
para variáveis ​​sed -i.bak 's /' $ search '/' $ replace '/ g' index.html
Fatima Zohra
33
no osx, use uma string vazia '' como parâmetro para -i, como:sed -i '' 's/blah/xx/g'
Pierre Houston
4
mas o que é seu .bakdepois sed -i?
Patrizio Bertoni
210

Um padrão alternativo e útil é:

sed -e 'script script' index.html > index.html.tmp && mv index.html.tmp index.html

Isso tem o mesmo efeito, sem usar a -iopção, e adicionalmente significa que, se o script sed falhar por algum motivo, o arquivo de entrada não será derrotado. Além disso, se a edição for bem-sucedida, não haverá mais nenhum arquivo de backup. Esse tipo de idioma pode ser útil nos Makefiles.

Muitos seds têm a -iopção, mas nem todos; o posix sed é aquele que não. Se você deseja portabilidade, é melhor evitar isso.

Norman Gray
fonte
9
+1 para nenhum arquivo de backup disponível e sem obstruir o arquivo de entrada se a edição falhar. Trabalhou perfeitamente no mac.
Mike Grace
Funcionou para mim perfeitamente. Obrigado! (em um Mac)
interessado
1
Isso funcionou perfeitamente para mim, onde no Ubuntu Server 14.04 sed -i ficava zerando o arquivo.
precisa
2
Aprimoramentos extremamente pequenos:... && mv index.html{.tmp,}
EdwardGarson /
5
@ EdwardGarson De fato, é provavelmente o que eu usaria se estivesse digitando - eu concordo que é melhor - mas sh(se bem me lembro) não tem essa {...}expansão. Em um Makefile, você pode estar usando um shpouco do que bash, por isso, se você deseja portabilidade (ou fixação), precisará evitar essa construção.
Norman Gray
95
sed -i 's/STRING_TO_REPLACE/STRING_TO_REPLACE_IT/g' index.html

Isso faz uma substituição global no local no arquivo index.html. A citação da sequência evita problemas com espaço em branco na consulta e substituição.

Rich Apodaca
fonte
57

use a opção -i do sed, por exemplo

sed -i bak -e s/STRING_TO_REPLACE/REPLACE_WITH/g index.html
Kevin
fonte
O que isto significa? sed: -i não podem ser utilizados com stdin
sheetal
2
Lembre-se de cercar o seu padrão entre aspas se contiver espaços em branco -'s/STRING_TO_REPLACE/REPLACE_WITH/g'
Doug Thompson
@sheetal: -irealiza edição de arquivos no local , para que não faça sentido combiná-lo com a entrada stdin .
precisa saber é o seguinte
Isso pode funcionar no macOS, mas não no Arch Linux para mim.
Xdevs23
Sem o -e, a resposta aceita não funciona no MacOS, Catalina. Com o -e funciona.
Cwhiii 24/10/19
18

Para alterar vários arquivos (e salvar um backup de cada um como * .bak):

perl -p -i -e "s/\|/x/g" *  

irá pegar todos os arquivos no diretório e substituí-lo |por x isso é chamado de “torta Perl” (fácil como uma torta)

Stenemo
fonte
1
É bom ver alguém disposto a olhar para a declaração do problema, e não apenas as tags. O OP não especificou sedcomo requisito, apenas o usou como a ferramenta já testada.
user7412956
14

Você deve tentar usar a opção -ide edição no local.

uloBasEI
fonte
6
sed -i.bak "s#https.*\.com#$pub_url#g" MyHTMLFile.html

Se você tiver um link a ser adicionado, tente isso. Pesquise o URL como acima (começando com https e terminando com.com aqui) e substitua-o por uma string de URL. Eu usei uma variável $pub_urlaqui. saqui significa pesquisa e gsubstituição global.

Funciona !

Kaey
fonte
6

Atenção: este é um método perigoso! Abusa dos buffers de E / S no Linux e, com opções específicas de buffer, ele consegue trabalhar em arquivos pequenos. É uma curiosidade interessante.Mas não o use para uma situação real!

Além da -iopção de sed você pode usar o teeutilitário .

De man:

tee - leia da entrada padrão e grave na saída e nos arquivos padrão

Portanto, a solução seria:

sed s/STRING_TO_REPLACE/STRING_TO_REPLACE_IT/g index.html | tee | tee index.html

- aqui o teeé repetido para garantir que o pipeline seja armazenado em buffer. Todos os comandos no pipeline são bloqueados até obterem alguma entrada para trabalhar. Cada comando no pipeline inicia quando os comandos upstream gravam 1 buffer de bytes (o tamanho é definido em algum lugar ) na entrada do comando. Então o último comandotee index.html , que abre o arquivo para gravação e, portanto, o esvazia, é executado após a conclusão do pipeline upstream e da saída no buffer dentro do pipeline.

Provavelmente, o seguinte não funcionará:

sed s/STRING_TO_REPLACE/STRING_TO_REPLACE_IT/g index.html | tee index.html

- ele executará os dois comandos do pipeline ao mesmo tempo sem nenhum bloqueio. (Sem bloquear o gasoduto deve passar a linha de bytes por linha em vez de tampão por tampão. O mesmo que quando você executar cat | sed s/bar/GGG/. Sem o bloqueio é mais interativo e geralmente pipelines de apenas 2 comandos executados sem buffer e bloqueando. Pipelines mais longos são tamponados.) A tee index.htmlvontade abra o arquivo para escrever e ele será esvaziado. No entanto, se você ativar o buffer sempre, a segunda versão também funcionará.

xealits
fonte
3
O arquivo de saída tee também é aberto imediatamente, resultando em um index.html vazio para o comando inteiro.
Sjngm
3
Isso corromperá qualquer arquivo de entrada que seja maior que o buffer do pipeline (que normalmente é de 64 KB) . (@sjngm: o arquivo não é truncado instantaneamente >, mas o ponto é que é uma solução quebrada que provavelmente resultará em perda de dados).
precisa saber é o seguinte
4

O problema com o comando

sed 'code' file > file

é aquele file é truncado pelo shell antes que o sed realmente o processe. Como resultado, você obtém um arquivo vazio.

A maneira mais fácil de fazer isso é usar -ipara editar no local, como outras respostas sugeridas. No entanto, isso nem sempre é o que você deseja. -icriará um arquivo temporário que será usado para substituir o arquivo original. Isso é problemático se o seu arquivo original era um link (o link será substituído por um arquivo normal). Se você precisar preservar os links, poderá usar uma variável temporária para armazenar a saída do sed antes de gravá-la no arquivo, desta forma:

tmp=$(sed 'code' file); echo -n "$tmp" > file

Melhor ainda, use em printfvez de echodesde que echoprovavelmente processará \\como \em algumas conchas (por exemplo, traço):

tmp=$(sed 'code' file); printf "%s" "$tmp" > file
Andrzej Pronobis
fonte
1
+1 para preservar links. Também funciona com um arquivo temporário:sed 'code' file > file.tmp; cat file.tmp > file; rm file.tmp
dashohoxha 26/11
3

E a edresposta:

printf "%s\n" '1,$s/STRING_TO_REPLACE/STRING_TO_REPLACE_IT/g' w q | ed index.html

Para reiterar o que o codaddict respondeu , o shell lida com o redirecionamento primeiro , eliminando o arquivo "input.html" e, em seguida, o shell chama o comando "sed" passando um arquivo agora vazio.

Glenn Jackman
fonte
2
pergunta rápida, por que as pessoas continuam dando "a edversão" das sedrespostas? ele executa mais rápido?
Cregox
6
Alguns seds não implementam -ipara editar no local. edé onipresente e permite salvar suas edições no arquivo original. Além disso, é sempre bom ter muitas ferramentas no seu kit.
Glenn Jackman
OK legal. então, em termos de desempenho, são os mesmos, suponho. obrigado!
Cregox
2

Você pode usar o Vim no modo Ex:

ex -sc '%s/STRING_TO_REPLACE/STRING_TO_REPLACE_IT/g|x' index.html
  1. % selecione todas as linhas

  2. x salvar e fechar

Steven Penny
fonte
0

Eu estava procurando a opção em que posso definir o intervalo de linhas e encontrei a resposta. Por exemplo, eu quero mudar host1 para host2 da linha 36-57.

sed '36,57 s/host1/host2/g' myfile.txt > myfile1.txt

Você também pode usar a opção gi para ignorar o caso dos caracteres.

sed '30,40 s/version/story/gi' myfile.txt > myfile1.txt

fonte
0

Com todo o respeito pelas respostas corretas acima, é sempre uma boa idéia "executar" scripts como esse, para que você não corrompa seu arquivo e precise reiniciar do zero.

Basta fazer com que seu script espalhe a saída na linha de comando em vez de gravá-la no arquivo, por exemplo, assim:

sed -e s/STRING_TO_REPLACE/STRING_TO_REPLACE_IT/g index.html

OU

less index.html | sed -e s/STRING_TO_REPLACE/STRING_TO_REPLACE_IT/g 

Dessa forma, você pode ver e verificar a saída do comando sem ter seu arquivo truncado.

Nestor Milyaev
fonte