Usando sed para encontrar e substituir cadeias complexas (de preferência com regex)

84

Eu tenho um arquivo com o seguinte conteúdo:

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

e preciso criar um script que altere o "nome" na primeira linha para "alguma coisa", a "senha" na segunda linha para "outra coisa" e o "nome" na terceira linha para "alguma coisa diferente". Como não posso confiar na ordem em que ocorrem no arquivo, não posso simplesmente substituir a primeira ocorrência de "nome" por "algo" e a segunda ocorrência de "nome" por "algo diferente". Na verdade, preciso fazer uma pesquisa pelas seqüências de caracteres circundantes para ter certeza de que estou encontrando e substituindo a coisa correta.

Até agora, tentei este comando para localizar 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 acho que alguns desses personagens podem precisar ser escapados etc.

Idealmente, eu adoraria poder usar regex para corresponder apenas às duas ocorrências de "nome de usuário" e substituir apenas o "nome". Algo assim, mas com sed:

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

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

Isso é possível?

Harry Muscle
fonte
2
Observe que praticamente qualquer solução baseada em regexp, a menos que seja extremamente artificial, corre o risco de quebrar a qualquer momento que o formato de entrada for alterado. Regexps são uma péssima escolha para lidar com XML, SGML ou derivados (o que isso me parece).
a CVn 07/06/2013
Aprovado! Considere usar o XQuery, por exemplo: w3schools.com/xquery/default.asp . Este é o padrão W3C para recuperar e manipular conteúdo XML.
precisa saber é o seguinte

Respostas:

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

Acho que é isso que você está procurando.

Explicação:

  • parênteses na primeira parte definem grupos (na verdade cadeias) que podem ser reutilizados na segunda parte
  • \1, \2etc. na segunda parte, são referências ao i-ésimo grupo capturado na primeira parte (a numeração começa com 1)
  • -Epermite expressões regulares estendidas (necessárias para +e agrupamento).
lgeorget
fonte
21
+1 para a opção -E
slackmart
4
que deixa para trás um arquivo de backup, com o nome (original name) + "-E".
Sarge Borsch
4
No OSX, recebo 'sed: 1: "s / (<nome do usuário>. +) Nome (. + ...": \ 1 não definido no RE'. Colei o exemplo exato desta pergunta em um arquivo. . i executou o comando a partir desta resposta sobre esse arquivo Talvez OSX tem uma sintaxe diferente?
deweydb
1
A versão gnu do sed suporta o parâmetro "-E", mas não é oficial. Nem é mencionado na página de manual. Se você quiser usar o regex estendido, precisará usar o parâmetro "-r".
Ikem Krueger
3
@deweydb De acordo com esta resposta , você deve usar \(e em \)vez de (e ).
Zhang Buzz
14
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/before antes sdiz ao sed para trabalhar apenas em linhas contendo a string 'username'.

evilsoup
fonte
1
Elegante, eficiente e perfeitamente adaptado ao estojo. +1
lgeorget
6

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

Se o seu arquivo for XML válido (não apenas essas três tags de aparência XML), você poderá 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

O exposto acima também funcionará em situações que seriam difíceis de resolver com expressões regulares:

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

Breve demonstração do acima exposto:

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>
homem a trabalhar
fonte
3

Você precisa citar \[.*^$/na parte da expressão regular do scomando e \&/na parte 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 scomando.

Você pode escolher um delimitador diferente para evitar a cotação /. Você precisará citar esse caractere, mas geralmente o ponto de alterar o delimitador é escolher um que não ocorra no texto a ser substituído 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 variações nessas partes.

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

sed -e 's~\(<username>.*[^A-Za-z]\[\)name\([^A-Za-z].*</username>\)~\1something\2~'
Gilles
fonte
3
$ 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 a "s", que indica o número da linha.

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

A. Rapariga
fonte
1

Para substituir a palavra "nome" pela palavra "alguma coisa", use:

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

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

Até agora, tudo é gerado na saída padrão, você pode usar:

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

para salvar as alterações em outro arquivo.

slackmart
fonte
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 
alfiogang
fonte