Usando sed para localizar e substituir uma string complexa (preferencialmente com regex)

54

Eu tenho um arquivo com o seguinte conteúdo:

<username><![CDATA[name]]></username>
<password><![CDATA[password]]></password>
<dbname><![CDATA[name]]></dbname>

e eu preciso fazer um script que mude o "nome" na primeira linha para "alguma coisa", a "senha" na segunda linha para "somethingelse", e o "nome" na terceira linha para "algo diferente" ". Eu não posso confiar na ordem destes ocorrendo no arquivo, então não posso simplesmente substituir a primeira ocorrência de "name" por "something" e a segunda ocorrência de "name" por "somethingdifferent". Na verdade, preciso pesquisar as sequências ao redor para ter certeza de que estou localizando e substituindo a coisa correta.

Até agora eu tentei este comando para encontrar e substituir a primeira ocorrência de "nome":

sed -i "s/<username><![CDATA[name]]><\/username>/something/g" file.xml

no entanto, não está funcionando, então estou pensando que alguns desses caracteres podem precisar de escape, etc.

Idealmente, eu adoraria poder usar o regex para combinar apenas as duas ocorrências de "nome de usuário" e substituir apenas o "nome". Algo parecido com isto, mas com sed :

<username>.+?(name).+?</username>

e substitua o conteúdo entre parênteses por "algo".

Isso é possível?

    
por Harry Muscle 07.06.2013 / 23:33

7 respostas

112

sed -i -E "s/(<username>.+)name(.+<\/username>)/something/" file.xml

Isto é, penso eu, o que você está procurando.

Explicação:

  • parênteses na primeira parte definem grupos (cadeias de fato) que podem ser reutilizados na segunda parte
  • , , etc. na segunda parte são referências ao i-ésimo grupo capturado na primeira parte (a numeração começa com 1)
  • -E ativa expressões regulares estendidas (necessárias para + e agrupamento).
por 07.06.2013 / 23:52
10
sed -e '/username/s/CDATA\[name\]/CDATA\[something\]/' \
-e '/password/s/CDATA\[password\]/CDATA\[somethingelse\]/' \
-e '/dbname/s/CDATA\[name\]/CDATA\[somethingdifferent\]/' file.txt

O /username/ antes do s diz ao sed para funcionar apenas nas linhas que contêm a string 'username'.

    
por 08.06.2013 / 00:05
4

Se sed não for um requisito difícil, use melhor uma ferramenta dedicada.

Se o seu arquivo é XML válido (não apenas aquelas 3 tags com aparência XML), então você pode usar o XMLStarlet :

xml ed -P -O -L \
  -u '//username/text()' -v 'something' \
  -u '//password/text()' -v 'somethingelse' \
  -u '//dbname/text()' -v 'somethingdifferent' file.xml

Os itens acima também funcionarão em situações que seriam difíceis de resolver com expressões regulares:

  • Pode substituir os valores das tags sem especificar seus valores atuais.
  • É possível substituir os valores mesmo se eles tiverem acabado de escapar e não estiverem incluídos no CDATA.
  • Pode substituir os valores mesmo se as tags tiverem atributos.
  • É possível substituir facilmente apenas as ocorrências de tags, se houver várias com o mesmo nome.
  • Pode formatar o XML modificado indentificando-o.

Breve demonstração do acima:

bash-4.2$ cat file.xml
<sith>
<master>
<username><![CDATA[name]]></username>
</master>
<apprentice>
<username><![CDATA[name]]></username>
<password>password</password>
<dbname foo="bar"><![CDATA[name]]></dbname>
</apprentice>
</sith>

bash-4.2$ xml ed -O -u '//apprentice/username/text()' -v 'something' -u '//password/text()' -v 'somethingelse' -u '//dbname/text()' -v 'somethingdifferent' file.xml
<sith>
  <master>
    <username><![CDATA[name]]></username>
  </master>
  <apprentice>
    <username><![CDATA[something]]></username>
    <password>somethingelse</password>
    <dbname foo="bar"><![CDATA[somethingdifferent]]></dbname>
  </apprentice>
</sith>
    
por 08.06.2013 / 19:58
2

Você precisa citar \[.*^$/ na parte de expressão regular do comando s e \&/ na peça de substituição, além de novas linhas. A expressão regular é uma expressão regular básica e, além disso, é necessário citar o delimitador para o comando s .

Você pode escolher um delimitador diferente para evitar a cotação de / . Você terá que citar esse caractere, mas geralmente o ponto de mudar o delimitador é escolher um que não ocorra no texto para substituir ou no texto de substituição.

sed -e 's~<username><!\[CDATA\[name\]\]></username>~<username><![CDATA[something]]></username>~'

Você pode usar grupos para evitar a repetição de algumas partes no texto de substituição e acomodar a variação dessas partes.

sed -e 's~\(<username><!\[[A-Z]*\[\)name\(\]\]></username>\)~something~'

sed -e 's~\(<username>.*[^A-Za-z]\[\)name\([^A-Za-z].*</username>\)~something~'
    
por 08.06.2013 / 02:15
2
$ sed -e '1s/name/something/2' \
      -e '3s/name/somethingdifferent/2' \
      -e 's/password/somethingelse/2' sample.xml

Você pode simplesmente usar endereços como no número anterior "s", que indica o número da linha.

Além disso, o número no final informa sed para substituir a segunda correspondência em vez de substituir a primeira correspondência.

    
por 18.09.2014 / 14:52
1

Para substituir a palavra "name" pela palavra "something", use:

sed "s/\(<username><\!\[[A-Z]*\[\)name\]/something/g" file.xml

Isso substituirá todas as ocorrências da palavra especificada.

Até agora, tudo é enviado para a saída padrão, você pode usar:

sed "s/\(<username><\!\[[A-Z]*\[\)name\]/something/g" file.xml > anotherfile.xml

para salvar as alterações em outro arquivo.

    
por 07.06.2013 / 23:55
0
Usage: sed [OPTION]... {script-only-if-no-other-script} [input-file]...

    -r, --regexp-extended
             use extended regular expressions in the script.

para substituir o valor em um arquivo de propriedades

sed -i -r 's/MAIL\=(.+)/MAIL\[email protected]/' etc/service.properties 
    
por 01.08.2018 / 10:21