Combine linhas e extraia conteúdo de uma só vez

1

Gostaria de encontrar linhas com h2 e p e obter o conteúdo entre as tags ...

<main>Nothing</main>
<h2>Hello</h2><p>World</p>
<h2>Bells</h2><p>Walls</p>
<h2>Jelly</h2><p>Minus</p>
<p>Fluff</p>

... em uma lista separada por tabulação:

Hello World
Bells Walls
Jelly Minus

Eu uso atualmente:

grep -E "<h2>(.*)<\/h2><p>(.*)<\/p>" | sed -E "s/<h2>(.*)<\/h2><p>(.*)<\/p>/ /"

No entanto, acho irritante ter que primeiro grep <pattern> e depois sed <the same pattern> . É possível fazer isso usando um único utilitário, usando o padrão apenas uma vez?

    
por forthrin 08.04.2018 / 21:42

4 respostas

2

Usar um analisador XML é realmente uma boa ideia, mas se você não puder usar um por algum motivo (o arquivo não está bem formado, você não tem nenhum analisador instalado, etc.), você pode usar o PERL para isso:

$ perl -ne 'if(/<h2>(.*?)<\/h2><p>(.*?)<\/p>/){print "$1\t$2\n"}' filename.ext
Hello   World
Bells   Walls
Jelly   Minus

Eu prefiro usar lazy matches para não obter resultados indesejados:

test.txt

<h1>Nothing</h1>
<h2>Hello</h2><p>World</p><h2>Goodbye</h2><p>Earth</p>
<h2>Bells</h2><p>Walls</p>
<h2>Jelly</h2><p>Minus</p>
<h3>Zip</h3>

$ perl -ne 'if(/<h2>(.*?)<\/h2><p>(.*?)<\/p>/){print "$1\t$2\n"}' test.txt
Hello   World
Bells   Walls
Jelly   Minus
$ perl -ne 'if(/<h2>(.*)<\/h2><p>(.*)<\/p>/){print "$1\t$2\n"}' test.txt
Hello</h2><p>World</p><h2>Goodbye       Earth
Bells   Walls
Jelly   Minus

Como você pode ver, usar somente uma expressão regular não obterá todos os casos que uma ferramenta específica de domínio usará. Se você está bem com isso, tudo bem; apenas esteja ciente de que você pode obter resultados imprecisos se a entrada não corresponder exatamente ao seu padrão!

    
por 08.04.2018 / 22:37
1

O caminho certo com a ferramenta xmlstarlet (para analisar dados xml / html):

xmlstarlet sel -t -m '//h2' -v 'concat(., "'$'\t''", ./following-sibling::p)' -n file

A saída:

Hello   World
Bells   Walls
Jelly   Minus
    
por 08.04.2018 / 22:19
1

Para o regex que você usa, que contém () sem aspas, precisa da sintaxe Regex estendida (ou substitua cada ( e ) por \( e \) ). Isso é simples.

E, provavelmente, evite uma combinação gananciosa usando [^<] em vez de um ponto.

Claro, você pode definir uma variável e jogar com aspas usando somente sed:

$ a='<h2>([^<]*)<\/h2><p>([^<]*)<\/p>'                                                                    
$ sed -nE '/'"$a"'/s/'"$a"'/ /p' infile

Mas fica melhor, pois isso poderia ser simplificado. Sed lembra o último regex usado e um lado esquerdo de s// (vazio) está em vigor.

$ sed -nE '/'"$a"'/s// /p' infile

Ou sem variável:

$ sed -nE '/<h2>([^<]*)<\/h2><p>([^<]*)<\/p>/s// /p' infile
Hello World
Bells Walls
Jelly Minus
    
por 08.04.2018 / 22:55
0

Possível solução via sed :

sed 's/<[^13>]*>/ /g' test | sed 's/<h[13]>.*<\/h[13]>//' <file>

 Hello  World
 Bells  Walls
 Jelly  Minus

Segundo sed apenas remove tags desnecessárias ( <h1> ou <h3> ).

Explicação do padrão:

/<[^13>]*>/ / - procure por qualquer símbolo * no texto que comece com < e termine com > . Mas entre as tags, os símbolos 1 ou 3 não devem ( ^ ) estar presentes.

    
por 08.04.2018 / 22:32