awk: parse e grave em outro arquivo

2

Eu tenho registros no arquivo XML como abaixo. Eu preciso procurar <keyword>SEARCH</keyword> e, se presente então eu preciso pegar o registro inteiro e escrever em outro arquivo (a partir de <record> to </record> )

Abaixo está o meu código awk que está dentro do loop. $1 retém o valor linha por linha de cada registro.

if(index($1,"SEARCH")>0)
{
print $1>> "output.txt"
}

Esta lógica tem dois problemas,

  1. Ele está escrevendo para output.txt file, apenas <keyword>SEARCH</keyword> element e não o registro inteiro (a partir de <record> to </record> )
  2. A pesquisa também pode estar presente na tag <detail> . Este código irá até escrever essa tag em output.txt

Arquivo XML:

<record category="xyz">
<person ssn="" e-i="E">
<title xsi:nil="true"/>
<position xsi:nil="true"/>
<names>
<first_name/>
<last_name></last_name>
<aliases>
<alias>CDP</alias>
</aliases>
<keywords>
<keyword xsi:nil="true"/>
<keyword>SEARCH</keyword>
</keywords>
<external_sources>
<uri>http://www.google.com</uri>
<detail>SEARCH is present in abc for xyz reason</detail>
</external_sources>
</details>
</record>
<record category="abc">
<person ssn="" e-i="F">
<title xsi:nil="true"/>
<position xsi:nil="true"/>
<names>
<first_name/>
<last_name></last_name>
<aliases>
<alias>CDP</alias>
</aliases>
<keywords>
<keyword xsi:nil="true"/>
<keyword>DONTSEARCH</keyword>
</keywords>
<external_sources>
<uri>http://www.google.com</uri>
<detail>SEARCH is not present in abc for xyz reason</detail>
</external_sources>
</details>
</record>
    
por user2488578 04.02.2016 / 11:11

3 respostas

7

Suponho que o que você postou é uma amostra, porque não é um XML válido. Se essa suposição não for válida, minha resposta não é válida ... mas, se for esse o caso, você realmente precisa atingir a pessoa que forneceu o XML com uma cópia agrupada da especificação XML e exigir consertá-lo '.

Mas realmente - awk e expressões regulares não são a ferramenta certa para o trabalho. Um analisador XML é. E com um analisador, é absurdamente simples fazer o que você quer:

#!/usr/bin/env perl

use strict;
use warnings;

use XML::Twig; 

#parse your file - this will error if it's invalid. 
my $twig = XML::Twig -> new -> parsefile ( 'your_xml' );
#set output format. Optional. 
$twig -> set_pretty_print('indented_a');

#iterate all the 'record' nodes off the root. 
foreach my $record ( $twig -> get_xpath ( './record' ) ) {
   #if - beneath this record - we have a node anywhere (that's what // means)
   #with a tag of 'keyword' and content of 'SEARCH' 
   #print the whole record. 
   if ( $record -> get_xpath ( './/keyword[string()="SEARCH"]' ) ) {
       $record -> print;
   }
}

xpath é bastante parecido com expressões regulares - de certa forma - mas é mais como um caminho de diretório. Isso significa que ele reconhece o contexto e pode manipular estruturas XML.

Acima: ./ significa 'abaixo do nó atual':

$twig -> get_xpath ( './record' )

Significa qualquer tag "top level" <record> .

Mas .// significa "em qualquer nível, abaixo do nó atual", por isso o fará recursivamente.

$twig -> get_xpath ( './/search' ) 

Obteria <search> nós em qualquer nível.

E os colchetes denotam uma condição - ou é uma função (por exemplo, text() para obter o texto do nó) ou você pode usar um atributo. por exemplo. //category[@name] encontraria qualquer categoria com um atributo de nome e //category[@name="xyz"] filtraria esses atributos.

XML usado para teste:

<XML>
<record category="xyz">
<person ssn="" e-i="E">
<title xsi:nil="true"/>
<position xsi:nil="true"/>
<details>
<names>
<first_name/>
<last_name></last_name>
</names>
<aliases>
<alias>CDP</alias>
</aliases>
<keywords>
<keyword xsi:nil="true"/>
<keyword>SEARCH</keyword>
</keywords>
<external_sources>
<uri>http://www.google.com</uri>
<detail>SEARCH is present in abc for xyz reason</detail>
</external_sources>
</details>
</person>
</record>
<record category="abc">
<person ssn="" e-i="F">
<title xsi:nil="true"/>
<position xsi:nil="true"/>
<details>
<names>
<first_name/>
<last_name></last_name>
</names>
<aliases>
<alias>CDP</alias>
</aliases>
<keywords>
<keyword xsi:nil="true"/>
<keyword>DONTSEARCH</keyword>
</keywords>
<external_sources>
<uri>http://www.google.com</uri>
<detail>SEARCH is not present in abc for xyz reason</detail>
</external_sources>
</details>
</person>
</record>
</XML>

Saída:

 <record category="xyz">
    <person
        e-i="E"
        ssn="">
      <title xsi:nil="true" />
      <position xsi:nil="true" />
      <details>
        <names>
          <first_name/>
          <last_name></last_name>
        </names>
        <aliases>
          <alias>CDP</alias>
        </aliases>
        <keywords>
          <keyword xsi:nil="true" />
          <keyword>SEARCH</keyword>
        </keywords>
        <external_sources>
          <uri>http://www.google.com</uri>
          <detail>SEARCH is present in abc for xyz reason</detail>
        </external_sources>
      </details>
    </person>
  </record>

Nota - o acima apenas imprime o registro para STDOUT. Isso é realmente ... na minha opinião, não é uma ótima idéia. Não menos importante porque - ele não imprime a estrutura XML e, portanto, não é realmente XML 'válido' se você tiver mais de um registro (não há um nó "raiz").

Então, eu preferiria: realizar exatamente o que você está perguntando:

#!/usr/bin/env perl

use strict;
use warnings;

use XML::Twig; 

my $twig = XML::Twig -> new -> parsefile ('your_file.xml'); 
$twig -> set_pretty_print('indented_a');

foreach my $record ( $twig -> get_xpath ( './record' ) ) {
   if ( not $record -> findnodes ( './/keyword[string()="SEARCH"]' ) ) {
       $record -> delete;
   }
}

open ( my $output, '>', "output.txt" ) or die $!;
print {$output} $twig -> sprint;
close ( $output ); 

Em vez disso - inverte a lógica e exclui (da estrutura de dados analisada na memória) os registros que você não quer e imprime toda a nova estrutura (incluindo cabeçalhos XML) em uma nova arquivo chamado "output.txt".

    
por 04.02.2016 / 11:31
1

Se eu entendi corretamente, isso pode ser uma solução no awk!:

/^<record/ {
    x1="";
    while (match($0, "record>$")==0)
    {
        x1=x1 $0"\n";
        getline;
    }
    x1=x1 $0;
    if (x1 ~ />SEARCH</)
    {
        print x1 > "output.txt";
    }
}

Isso extrairá os blocos, registre > para \ record & gt ;, contendo a chave "SEARCH" dentro, para o arquivo de saída.

    
por 04.02.2016 / 13:01
0

Além disso, awk (mesmo que e outros processadores de texto) não é uma ferramenta de análise xml correta:

awk '
    lines{
        lines=lines "\n" $0
    }
    /<\/record/{
        if(lines ~ /keyword>SEARCH</)
            print lines
        lines=""
    }
    /<record/{
        lines=$0
    }
    ' <input.txt >output.txt

O mesmo que sed

sed -n '/<record/{:1;N;/<\/record/!b1;/keyword>SEARCH</p;}' <input.txt >output.txt
    
por 04.02.2016 / 14:40