arquivo de edição sed no lugar

300

Estou tentando descobrir se é possível editar um arquivo em um único comando sed sem transmitir manualmente o conteúdo editado para um novo arquivo e renomear o novo arquivo para o nome do arquivo original. Eu tentei a -iopção, mas meu sistema Solaris disse que -ié uma opção ilegal. Existe uma maneira diferente?

anfíbio
fonte
7
-ié uma opção no gnu sed, mas não está no sed padrão. No entanto, ele transmite o conteúdo para um novo arquivo e renomeia o arquivo para que não seja o que você deseja.
William Pursell
2
na verdade, é o que eu quero, eu só quero não ser exposto a ter que realizar a tarefa mundana de renomear o novo arquivo para o nome original
amphibient
3
Então você precisa reafirmar a pergunta.
William Pursell
3
@ anfphient: Você se importaria de prefixar o título da sua pergunta com a palavra 'Solaris'? O valor da sua pergunta está sendo perdido. Por favor, veja os comentários abaixo da minha resposta. Obrigado.
Steve
1
@ Steve: Eu removi o prefixo Solaris do título novamente, porque isso não é de forma alguma exclusivo do Solaris.
tripleee

Respostas:

477

A -iopção transmite o conteúdo editado para um novo arquivo e o renomeia nos bastidores.

Exemplo:

sed -i 's/STRING_TO_REPLACE/STRING_TO_REPLACE_IT/g' filename

e

sed -i '' 's/STRING_TO_REPLACE/STRING_TO_REPLACE_IT/g' filename

no macOS .

choroba
fonte
3
pelo menos ele faz isso por mim, então eu não tenho que
amphibient
2
Você pode tentar compilar o GNU sed no seu sistema, então.
choroba
3
exemplo:sed -i "s/STRING_TO_REPLACE/STRING_TO_REPLACE_IT/g" <file>
Thales Ceolin
2
Isso é melhor que a solução perl. De alguma forma, ele não cria nenhum arquivo .swap .... obrigado!
maths
3
@ maths: Sim, mas talvez em outro lugar ou por um tempo mais curto?
choroba
72

Em um sistema em sedque não há a capacidade de editar arquivos, acho que a melhor solução seria usar perl:

perl -pi -e 's/foo/bar/g' file.txt

Embora isso crie um arquivo temporário, ele substitui o original porque um sufixo / extensão vazio no local foi fornecido.

Steve
fonte
14
Além de criar um arquivo temporário, ele também quebra os links físicos (por exemplo, em vez de alterar o conteúdo do arquivo, ele substitui o arquivo por um novo arquivo com o mesmo nome.) Às vezes, esse é o comportamento desejado, às vezes é aceitável, mas quando há vários links para um arquivo, quase sempre é o comportamento errado.
William Pursell #
3
@ DwightSpencer: Eu acredito que todos os sistemas sem Perl estão quebrados. Um único comando supera uma cadeia de comandos quando alguém deseja permissões elevadas e deve usá-lo sudo. Na minha opinião, a questão é sobre como evitar criar e renomear manualmente um arquivo temporário sem ter que instalar o GNU ou BSD mais recente sed. Basta usar Perl.
20914 Steve Steve
4
@PetrPeller: Sério? Se você ler a pergunta com atenção, entenderia que o OP está tentando evitar algo como sed 's/foo/bar/g' file.txt > file.tmp && mv file.tmp file.txt,. Só porque a edição no local renomeia usando um arquivo temporário, não significa que ele deve executar uma renomeação manual quando a opção não estiver disponível. Existem outras ferramentas por aí que podem fazer o que ele / ela quer, e Perl é a escolha óbvia. Como isso não é uma resposta para a pergunta original?
Steve Steve
2
@a_arias: Você está certo; ele fez. Mas ele não pode alcançar o que deseja usando o Solaris sed. A questão era realmente muito bom, mas é valor foi diminuído por pessoas que ou não podem ler as páginas man ou não pode ser incomodado para realmente perguntas lido aqui no SO.
Steve
4
@EdwardGarson: IIRC, o Solaris é enviado com Perl, mas não com Python ou Ruby. E, como eu disse antes, o OP não pode alcançar o resultado desejado usando o Solaris sed.
23715 Steve Steve
56

Observe que no OS X você pode obter erros estranhos como "código de comando inválido" ou outros erros estranhos ao executar este comando. Para corrigir este problema, tente

sed -i '' -e "s/STRING_TO_REPLACE/STRING_TO_REPLACE_IT/g" <file>

Isso ocorre porque na versão OSX de sed, a -iopção espera um extensionargumento para que seu comando seja realmente analisado como extensionargumento e o caminho do arquivo seja interpretado como o código de comando. Fonte: https://stackoverflow.com/a/19457213

diadyne
fonte
1
Essa é outra singularidade do OS X. Felizmente, um brew install gnu-sedjunto com um PATHpermitirá que você use a versão GNU do sed. Obrigado por mencionar; um arranhador de cabeça definitivo.
Doug
23

O seguinte funciona bem no meu mac

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

Estamos substituindo foo por bar no arquivo de amostra. O backup do arquivo original será salvo no sample.bak

Para editar em linha sem backup, use o seguinte comando

sed -i'' 's/foo/bar/g' sample
minhas23
fonte
De alguma forma, como evitar manter arquivos de backup?
precisa saber é o seguinte
@PetrPeller, você pode usar o comando mencionado para o mesmo ... sed -i '' 's / foo / bar / g' sample
minhas23
18

Uma coisa a observar, sednão é possível gravar arquivos por si só, pois o único objetivo do sed é atuar como um editor no "fluxo" (ou seja, pipelines de stdin, stdout, stderr e outros >&nbuffers, soquetes e similares). Com isso em mente, você pode usar outro comando teepara gravar a saída de volta no arquivo. Outra opção é criar um patch para canalizar o conteúdo diff.

Método Tee

sed '/regex/' <file> | tee <file>

Método de correção

sed '/regex/' <file> | diff -p <file> /dev/stdin | patch

ATUALIZAR:

Além disso, observe que o patch fará com que o arquivo mude da linha 1 da saída do diff:

O patch não precisa saber qual arquivo acessar, pois é encontrado na primeira linha da saída do diff:

$ echo foobar | tee fubar

$ sed 's/oo/u/' fubar | diff -p fubar /dev/stdin
*** fubar   2014-03-15 18:06:09.000000000 -0500
--- /dev/stdin  2014-03-15 18:06:41.000000000 -0500
***************
*** 1 ****
! foobar
--- 1 ----
! fubar

$ sed 's/oo/u/' fubar | diff -p fubar /dev/stdin | patch
patching file fubar
Dwight Spencer
fonte
2
/dev/stdin 2014-03-15, respondeu 7 de janeiro - você é um viajante do tempo?
Adrian Frühwirth
No Windows, usando o msysgit, /dev/stdinnão existe; portanto, você deve substituir /dev/stdinpor '-', um único hífen sem as aspas, para que os seguintes comandos funcionem: $ sed 's/oo/u/' fubar | diff -p fubar -e$ sed 's/oo/u/' fubar | diff -p fubar - | patch
Jim Raden
@ AdrianFrühwirth Eu tenho uma caixa policial azul em algum lugar e, por algum motivo, eles me chamam de médico no trabalho. Mas, honestamente, parece que há um desvio muito ruim do sistema no momento.
Dwight Spencer
Essas soluções me parecem baseadas em um determinado comportamento de buffer. Eu suspeito que, para arquivos maiores, esses podem quebrar, enquanto o sed está lendo, o arquivo pode começar a mudar por baixo pelo comando patch ou, pior ainda, pelo comando tee que truncará o arquivo. Na verdade, não tenho certeza se patchisso também não será truncado. Eu acho que salvar a saída em outro arquivo e depois catinseri-lo no original seria mais seguro.
akostadinov 26/05
reais no local resposta de @ william-Pursell
akostadinov
11

Não é possível fazer o que você quer sed. Mesmo versões seddesse suporte à -iopção de editar um arquivo no local fazem exatamente o que você declarou explicitamente que não deseja: elas gravam em um arquivo temporário e depois renomeiam o arquivo. Mas talvez você possa apenas usar ed. Por exemplo, para alterar todas as ocorrências de foopara barno arquivo file.txt, você pode:

echo ',s/foo/bar/g; w' | tr \; '\012' | ed -s file.txt

A sintaxe é semelhante sed, mas certamente não é exatamente a mesma.

Mas talvez você deva considerar por que não deseja usar um arquivo temporário renomeado. Mesmo se você não tiver -isuporte sed, poderá escrever facilmente um script para fazer o trabalho por você. Em vez de sed -i 's/foo/bar/g' file, você poderia fazer inline file sed 's/foo/bar/g'. Esse script é trivial para escrever. Por exemplo:

#!/bin/sh -e
IN=$1
shift
trap 'rm -f $tmp' 0
tmp=$( mktemp )
<$IN "$@" >$tmp && cat $tmp > $IN  # preserve hard links

deve ser adequado para a maioria dos usos.

William Pursell
fonte
1
Por exemplo, como você nomearia esse script e como o chamaria?
anfphient
2
Eu o chamaria inlinee o chamaria como descrito acima:inline inputfile sed 's/foo/bar/g'
William Pursell
1
uau, edapenas responda o que realmente funciona. Primeira vez que eu preciso ed. Obrigado. Uma pequena otimização é usar echo -e ",s/foo/bar/g\012 w"ou echo $',s/foo/bar/g\012 w'onde o shell permite evitar trchamadas extras .
akostadinov 26/05
2
edrealmente não faz as modificações no local. A partir da stracesaída, o GNU edcria um arquivo temporário, grava as alterações no arquivo temporário e, em seguida, reescreve o arquivo original inteiro, preservando os links físicos. Na trusssaída, o Solaris 11 edtambém usa um arquivo temporário, mas renomeia o arquivo temporário para o nome do arquivo original ao salvar com o wcomando, destruindo os links físicos.
Andrew Henle
@AndrewHenle Isso é desanimador. O edque acompanha os macos atuais (Mojave, qualquer que seja o significado do jargão de marketing) ainda preserva os hardlinks. YMMV
William Pursell /
10

Você poderia usar vi

vi -c '%s/foo/bar/g' my.txt -c 'wq'
mench
fonte
9

O sed suporta edição no local. De man sed:

-i[SUFFIX], --in-place[=SUFFIX]

    edit files in place (makes backup if extension supplied)

Exemplo :

Digamos que você tenha um arquivo hello.txtcom o texto:

hello world!

Se você deseja manter um backup do arquivo antigo, use:

sed -i.bak 's/hello/bonjour' hello.txt

Você terminará com dois arquivos: hello.txtcom o conteúdo:

bonjour world!

e hello.txt.bakcom o conteúdo antigo.

Se você não deseja manter uma cópia, apenas não passe o parâmetro extension.

Sergio A.
fonte
3
O OP menciona especificamente que sua plataforma não suporta essa opção (popular, mas) fora do padrão.
tripleee
3

Você não especificou qual shell está usando, mas com o zsh você pode usar a =( )construção para conseguir isso. Algo ao longo das linhas de:

cp =(sed ... file; sync) file

=( )é semelhante a, >( )mas cria um arquivo temporário que é excluído automaticamente quando cpfinalizado.

Thor
fonte
3
mv file.txt file.tmp && sed 's/foo/bar/g' < file.tmp > file.txt

Deve preservar todos os hardlinks, pois a saída é direcionada para substituir o conteúdo do arquivo original e evita a necessidade de uma versão especial do sed.

JJM
fonte
3

Se você estiver substituindo a mesma quantidade de caracteres e depois de ler atentamente a edição "in-loco" dos arquivos ...

Você também pode usar o operador de redirecionamento <>para abrir o arquivo para ler e escrever:

sed 's/foo/bar/g' file 1<> file

Veja ao vivo:

$ cat file
hello
i am here                           # see "here"
$ sed 's/here/away/' file 1<> file  # Run the `sed` command
$ cat file
hello
i am away                           # this line is changed now

No Bash Reference Manual → 3.6.10 Abrindo descritores de arquivo para leitura e gravação :

O operador de redirecionamento

[n]<>word

faz com que o arquivo cujo nome é a expansão da palavra seja aberto para leitura e gravação no descritor de arquivo n ou no descritor de arquivo 0 se n não for especificado. Se o arquivo não existir, ele será criado.

fedorqui 'Então pare de prejudicar'
fonte
Isso está corrompido pelo arquivo. Não tenho certeza do motivo por trás disso. paste.fedoraproject.org/462017
J Wani
Veja as linhas [43-44], [88-89], [135-136]
Nehal J Wani
2
Este post do blog explica por que essa resposta não deve ser usada. backreference.org/2011/01/29/in-place-editing-of-files
Nehal J Wani
@NehalJWani Terei uma longa leitura do artigo, obrigado pelo aviso! O que não mencionei na resposta é que isso funciona se alterar a mesma quantidade de caracteres.
fedorqui 'Então pare de prejudicar'
@NehalJWani, sendo assim, desculpe-me se minha resposta causou danos aos seus arquivos. Vou atualizá-lo com correções (ou excluí-lo, se necessário) depois de ler os links que você forneceu.
fedorqui 'Então, pare de prejudicar'
2

Como Moneypenny disse em Skyfall: "Às vezes, os modos antigos são os melhores". Kincade disse algo semelhante mais tarde.

$ printf ',s/false/true/g\nw\n' | ed {YourFileHere}

Edição feliz no local. Adicionado '\ nw \ n' para gravar o arquivo. Desculpas pelo atraso na resposta ao pedido.

jlettvin
fonte
Você pode explicar o que isso faz?
precisa saber é o seguinte
1
Invoca ed (1), o editor de texto com alguns caracteres escritos para stdin. Como alguém com menos de 30 anos, acho que posso dizer que ed (1) é para os dinossauros como os dinossauros são para nós. Enfim, em vez de abrir e digitar essas coisas, jlettvin está usando os caracteres |. @MattMontag
Ari Sweedler
Se você está perguntando o que os comandos fazem, há dois deles, terminados por a \n. [range] s / <old> / <novo> / <flag> é a forma do comando substituto - um paradigma ainda visto em muitos outros editores de texto (ok, vim, um descendente direto de ed) De qualquer forma, a gbandeira significa "global" e significa "Cada instância em cada linha que você vê". O intervalo 3,5analisa as linhas 3-5. ,5olha StartOfFile-5, 3,olha as linhas 3-EndOfFile e ,olha StartOfFile-EndOfFile. Um comando de substituição global em todas as linhas que substituem "false" por "true". Em seguida, o comando de gravação,, wé inserido.
Ari Sweedler
1

Muito bons exemplos. Eu tive o desafio de editar vários arquivos e a opção -i parece ser a única solução razoável para usá-lo no comando find. Aqui o script para adicionar "version:" na frente da primeira linha de cada arquivo:

find . -name pkg.json -print -exec sed -i '.bak' '1 s/^/version /' {} \;
Robert
fonte