sed first n ocorrências conjunto de resultados entre 2 tags / patterns

4

Eu tenho um arquivo XML grande e obtenho todas as ocorrências entre duas tags:

Aqui está o que eu faço:

sed -n '/<tag>/,/<\/tag>/p' file.xml

E preciso filtrar para obter apenas as primeiras N ocorrências. Eu tentei com l param mas não foi suficiente: (

Então, alguém sabe como obter N ocorrências correspondentes de todos os resultados?

Por exemplo. Aqui o conteúdo do arquivo xml:

<?xml version="1.0" encoding="UTF-8"?>
<root>
<tag>
 <t1>john</t1>
 <t2>john</t2>
 <t3>john</t3>
</tag>
<tag>
 <t1>john</t1>
 <t2>john</t2>
 <t3>john</t3>
</tag>
<tag>
 <t1>john</t1>
 <t2>john</t2>
 <t3>john</t3>
</tag>
<tag>
 <t1>john</t1>
 <t2>john</t2>
 <t3>john</t3>
</tag>
</root>

 sed -n '/<tag>/,/<\/tag>/p' file.xml 

retorna todos os elementos.

Portanto, o objetivo é filtrar para obter os Primeiros n padrões combinados (os elementos são multi-linha) Se n = 2, então resultado =:

<tag>
 <t1>john</t1>
 <t2>john</t2>
 <t3>john</t3>
</tag>
<tag>
 <t1>john</t1>
 <t2>john</t2>
 <t3>john</t3>
</tag>
    
por TVart 03.06.2014 / 12:26

6 respostas

3

Você realmente deveria estar usando um analisador para isso, mas, só assim você sabe, sed -n '/<tag>/,/<\/tag>/p' file.xml obtém todos os elementos porque você p rint todos eles. Esse comando funciona endereçando todas as linhas entre uma linha contendo <tag> e a próxima linha na entrada que contém </tag> . Como isso faz praticamente todas as suas linhas, apenas p rinting elas não mostram muita diferença. Algo como o seguinte pode ser um pouco mais próximo da marca:

sed -n '\|<tag>|{:n
    \|</tag>|!{N;bn}
    y|\n| |;p
}'

Ele aborda <tag> linhas e as verifica em </tag> . Se eles não contiverem a cadeia de fechamento, ela puxará outra linha - e fará isso repetidamente até que o espaço de padrão contenha <tag>.*</tag>[^\n]*$ .

Então, eu só traduzo todos os caracteres de ewline \n no espaço padrão para espaços.

Aqui está novamente:

sed -n '\|<tag>|{:n;\|</tag>|!{N;bn};y|\n| |;p}' <<\DATA
<?xml version="1.0" encoding="UTF-8"?>
<root>
<tag>
 <t1>john</t1>
 <t2>john</t2>
 <t3>john</t3>
</tag>
<tag>
 <t1>john</t1>
 <t2>john</t2>
 <t3>john</t3>
</tag>
<tag>
 <t1>john</t1>
 <t2>john</t2>
 <t3>john</t3>
</tag>
<tag>
 <t1>john</t1>
 <t2>john</t2>
 <t3>john</t3>
</tag>
</root>
DATA

OUTPUT:

<tag>  <t1>john</t1>  <t2>john</t2>  <t3>john</t3> </tag>
<tag>  <t1>john</t1>  <t2>john</t2>  <t3>john</t3> </tag>
<tag>  <t1>john</t1>  <t2>john</t2>  <t3>john</t3> </tag>
<tag>  <t1>john</t1>  <t2>john</t2>  <t3>john</t3> </tag>

Agora você pode fazer:

sed -n '\|<tag>|{:n
    \|</tag>|!{N;bn}
    y|\n| |;p
}' ./file | 
sed 's|> |>\n|g;2q'

... o que me pega:

<tag>
 <t1>john</t1>
 <t2>john</t2>
 <t3>john</t3>
</tag>
<tag>
 <t1>john</t1>
 <t2>john</t2>
 <t3>john</t3>
</tag>
    
por 05.06.2014 / 22:12
4

Tente:

xmllint --xpath '//tag[position()<=2]' file.xml

Ou:

xmlstarlet sel -t -c '//tag[position()<=2]' file.xml

Ou:

xmlstarlet sel -t -m '//tag[position()<=2]' -c . -n file.xml

Se você quisesse fazer isso com sed , só poderia fazer algo como:

sed -n '
  1{x;s/^/../;x;}; # initialise counter with two tokens
  /<tag>/,/<\/tag>/ {
    p; /<\/tag>/{
      x;s/.//;/./!q;x; # remove a token and quit if hold space empty
    }
  }' file.xml

Ou seja, use o espaço de espera como um contador de seções restantes para exibir (usando caracteres de ponto).

    
por 05.06.2014 / 21:32
1

Eu acho que isso é o que você quer,

sed -n '/<tag>/,/<\/tag>/p' file.xml | head -10

Experimente o comando abaixo para obter as duas primeiras linhas que começam com <tag> ,

$ sed -n '/^<tag>/p' file.xml | head -2
<tag><t1>john</t1></tag>
<tag><t1>john</t1></tag>
    
por 03.06.2014 / 12:35
0

Tubo pela cabeça - N . Após as primeiras N ocorrências, a cabeça sai e assim parará.

    
por 03.06.2014 / 12:30
0

Tanto quanto sei, sed correspondências são sempre gananciosas, ou seja, /<tag>/,/<\/tag>/ corresponderá da primeira instância de <tag> à última instância de <\tag> - incluindo quaisquer outros objetos XML entre.

Se a sua versão de awk suportar separadores de registros com vários caracteres, você poderá fazer algo como

awk -v n=2 'BEGIN{RS="</tag>\n";ORS=RS} NR<=n'

mas realmente uma solução mais robusta seria usar um analisador XML dedicado - por exemplo, uma implementação muito mínima usando o minidom

do python
#!/usr/bin/python

from xml.dom import minidom

xmldoc = minidom.parse('file.xml')
taglist = xmldoc.getElementsByTagName('tag')
for i in range(2) :
        print taglist[i].toxml()
    
por 03.06.2014 / 17:07
0

Bem, eventualmente, responderei a minha própria pergunta.

A solução que encontrei funciona em 2 (talvez 3) etapas:

1 - Obtendo todos os elementos obrigatórios por:

sed -n '/<tag>/,/<\/tag>/p' file.xml > selectedItems.xml

2 - Obtendo a N-ésima posição do último item por

POS = grep -n '</tag>' ./selectedItems.xml | head -n [POS] | tail -n 1

3 - Obtenção dos primeiros N itens obrigatórios:

sed -n 1,[POS]p selectedItems.xml > selectedItems.xml

Claro que é possível fazer todos os passos sem separar, mas não ficará tão claro.

P.S. Para ter certeza de que a posição corresponde à posição real de N-ésimo na árvore (quando o arquivo xml é formado todo em linha) eu costumava usar:

xmllint --format ./myxmlfile.xml
    
por 04.06.2014 / 10:29

Tags