Use ed
. Está disponível na maioria das plataformas e pode editar seus arquivos no local.
Como sed
é baseado em ed
, a sintaxe para substituir padrões é semelhante:
ed -s infile <<\IN
,s/old/new/g
w
q
IN
Eu tenho um requisito em meu projeto para substituir algum texto existente em um arquivo como foo
com outro texto como fooofoo
:
abc.txt
name
foo
foo1
Então eu tentei:
sed -i "s/foo/fooofoo/g" abc.txt
No entanto, recebo este erro:
sed: illegal option --
i
Eu encontrei no manual que tenho que usar:
sed -i\ "s/foo/fooofoo/g" abc.txt
No entanto, isso não está funcionando também.
Também encontrei alternativas em perl
e awk
, mas uma solução no Solaris sed
seria muito apreciada.
Estou usando esta versão do bash:
GNU bash, version 3.2.57(1)-release (sparc-sun-solaris2.10)
Use ed
. Está disponível na maioria das plataformas e pode editar seus arquivos no local.
Como sed
é baseado em ed
, a sintaxe para substituir padrões é semelhante:
ed -s infile <<\IN
,s/old/new/g
w
q
IN
Se você não pode instalar o GNU sed, use:
sed "s/foo/fooofoo/g" abc.txt >abc.tmp && mv abc.tmp abc.txt
Isso usa redirecionamento para enviar a saída do sed para um arquivo temporário. Se a sed for concluída com êxito, isso substitui abc.txt
pelo arquivo temporário.
Como pode ser visto no código fonte do GNU sed , isso é exatamente o que o sed -i
faz. Então, isso é tão eficiente quanto sed -i
.
Se houver uma chance de que abc.tmp
já exista, convém usar mktemp
ou um utilitário semelhante para gerar o nome exclusivo para o temporário.
Se você quer o equivalente a sed -i.bak
, é bem simples.
Considere este script para o GNU sed:
#!/bin/sh
# Create an input file to demonstrate
trap 'rm -r "$dir"' EXIT
dir=$(mktemp -d)
grep -v '[[:upper:][:punct:]]' /usr/share/dict/words | head >"$dir/foo"
# sed program - removes 'aardvark' and 'aardvarks'
script='/aard/d'
##########
# What we want to do
sed -i.bak -e "$script" "$dir"
##########
# Prove that it worked
ls "$dir"
cat "$dir/foo"
Podemos simplesmente substituir a linha marcada por
cp "$dir/foo" "$dir/foo.bak" && sed -e "$script" "$dir/foo.bak" >"$dir/foo"
Isso move o arquivo existente para um backup e grava um novo arquivo.
Se quisermos o equivalente a
sed -i -e "$script" "$dir" # no backup
então é um pouco mais complexo. Podemos abrir o arquivo para leitura como entrada padrão e, em seguida, desvinculá-lo antes de direcionar a saída do sed para substituí-lo:
( cp "$dir/foo" "$dir/foo.bak"; exec <"$dir/foo.bak"; rm "$dir/foo.bak"; exec sed -e "$script" >"$dir/foo" )
Fazemos isso em um sub-shell, para que o stdin original ainda esteja disponível depois disso. É possível trocar as entradas e voltar sem um subshell, mas desta forma parece mais claro para mim.
Observe que temos o cuidado de copiar primeiro, em vez de criar um novo arquivo foo
- isso é importante se o arquivo for conhecido por mais de um nome (ou seja, tiver links físicos) e você quiser ter certeza de que não quebre os links.
Primeiro, você deve saber que o comando i\
ao qual você está se referindo é para inserir uma linha de texto - não para salvar o texto editado de volta no arquivo. Não há nenhuma maneira especificada pelo POSIX para usar sed
para isso.
O que você pode fazer é usar ex
, que é especificado por POSIX :
printf '%s\n' '%s/find/replace/g' 'x' | ex file.txt
O comando x
é para "salvar e sair". Você também pode usar wq
, mas x
é menor.
O sinal %
no início do comando substituto significa "Aplicar este comando a todas as linhas no buffer". Você pode usar 1,$s/find/replace/g
também.
Uma grande diferença entre ex
e sed
é que sed
é um editor stream . Apenas opera sequencialmente, linha por linha. ex
é muito mais flexível do que isso e, de fato, você pode fazer a edição interativa de arquivos de texto diretamente em ex
. É o antecessor imediato de vi
.
sed
e nenhum arquivo temporário visível : Você pode evitar a criação de um "arquivo temporário visível ":
exec 3<abc.txt
rm abc.txt
sed 's/foo/fooofoo/' <&3 >abc.txt
exec 3<&-
Sistemas semelhantes a Unix não removem o conteúdo do arquivo do disco até que ele seja ambos desvinculado no sistema de arquivos, e não abertos em nenhum processo. Portanto, você pode fazer exec 3<
para abrir o arquivo no shell para ler no descritor de arquivo 3, rm
o arquivo (que o desvincula do sistema de arquivos) e, em seguida, chamar sed
com o descritor de arquivo 3 usado como entrada .
Note que isso é muito diferente disso:
# Does not work.
sed 's/foo/fooofoo/' <abc.txt >abc.txt
A diferença é que quando você faz isso em um comando, o shell apenas abre o mesmo arquivo para ambas as leituras, e para escrever com a opção de truncar o arquivo - como ainda é o mesmo arquivo, você perde o conteúdo. Mas se você abri-lo para leitura, então rm
it, então abra o mesmo nome de caminho para escrita, você está criando um novo arquivo no mesmo nome de caminho (mas em um novo inode e local de disco, pois o original ainda está aberto ): então o conteúdo ainda está disponível.
Depois de terminar, feche o descritor de arquivo que você abriu anteriormente (é o que a sintaxe exec 3<&-
especial), que libera o arquivo original para que o sistema operacional exclua (marque como não usado) seu espaço em disco .
Há algumas coisas que você deve ter em mente sobre essa solução:
Você só obtém um "go" através do conteúdo - não há portable maneira de um shell "procurar" de volta no descritor de arquivo - então, uma vez que um programa lê algum conteúdo , outros programas só verão o restante do arquivo. E sed
lerá o arquivo inteiro.
Há uma pequena chance do seu arquivo original ser perdido se seu shell / script / sed for morto antes de ser feito.
Tags text-processing sed solaris