Usando sed para localizar e substituir strings complexas (de preferência com regex)

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 altere o” nome “na primeira linha para” algo “, a” senha “na segunda linha para” algoelse “e o” nome “em a terceira linha para “algo diferente”. Não posso confiar na ordem dessas ocorrências no arquivo, portanto, 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 strings ao redor 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 estou pensando que alguns desses caracteres podem precisar de escape, etc.

Idealmente, eu ” Adoraria ser capaz de usar regex para apenas combinar as 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 dos colchetes por “algo”.

Isso é possível?

Comentários

  • Observe que praticamente qualquer solução baseada em regexp, a menos que extremamente planejada, corre o risco quebrando sempre que o formato de entrada muda. Regexps são uma escolha ruim para lidar com XML, SGML ou derivados (o que me parece).
  • Aprovado! Considere o uso de XQuery, por exemplo: w3schools.com/xquery/default.asp . Este é o padrão W3C para recuperar e manipular conteúdo XML.

Resposta

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

Isso é, eu acho, o que você está procurando.

Explicação:

  • os parênteses na primeira parte definem grupos (strings na verdade) que podem ser reutilizados na segunda parte
  • \1, \2, 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).

Comentários

  • +1 para a opção -E
  • it deixa para trás um arquivo de backup, com o nome (original name) + "-E".
  • No OSX eu obtenho ‘ sed: 1: ” s / (< nome de usuário >. +) nome (. + … “: \ 1 não definido no RE ‘. Colei o exemplo exato desta pergunta em um arquivo. então eu executei o comando desta resposta naquele arquivo. Talvez o OSX tenha uma sintaxe diferente?
  • A versão gnu do sed oferece suporte ao parâmetro ” -E “, mas não oficial. Ele ‘ nem mesmo é mencionado na página de manual. Se quiser usar o regex estendido, você deverá usar o parâmetro ” -r “.
  • @ deweydb De acordo com esta resposta , você deve usar \( e \) em vez de ( e ).

Resposta

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 em linhas que contenham a string “nome de usuário”.

Comentários

  • Elegante, eficiente e perfeitamente adequado para o caso. +1

Resposta

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

Se o seu arquivo for XML válido (não apenas aquelas 3 tags que parecem XML), então você pode usar XMLStarlet :

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

O 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 apenas escape e não estiverem incluídos em CDATA.
  • Pode substituir os valores mesmo se as tags têm atributos.
  • Pode facilmente substituir apenas as ocorrências de tags, se houver vários com o mesmo nome.
  • Pode formatar o XML modificado recuando-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> 

Resposta

$ sed -e "1s/name/something/2" \ -e "3s/name/somethingdifferent/2" \ -e "s/password/somethingelse/2" sample.xml 

Você pode simplesmente usar endereços de s no número anterior a “s” que indica o número da linha.

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

Resposta

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

Você pode escolher um delimitador diferente para evitar a necessidade de citar /. Você terá que citar esse caractere, mas normalmente o objetivo 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~" 

Resposta

Para substituir a palavra “nome” pela palavra “algo”, use:

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

Isso vai 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\]/\1something/g" file.xml > anotherfile.xml 

para salvar as alterações em outro arquivo.

Resposta

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 

Deixe uma resposta

O seu endereço de email não será publicado. Campos obrigatórios marcados com *