Come estrarre una stringa seguendo uno schema con grep, regex o perl


90

Ho un file che assomiglia a questo:

    <table name="content_analyzer" primary-key="id">
      <type="global" />
    </table>
    <table name="content_analyzer2" primary-key="id">
      <type="global" />
    </table>
    <table name="content_analyzer_items" primary-key="id">
      <type="global" />
    </table>

Devo estrarre qualsiasi cosa all'interno delle virgolette che seguono name=, ovvero content_analyzer, content_analyzer2e content_analyzer_items.

Lo sto facendo su una macchina Linux, quindi una soluzione che usa sed, perl, grep o bash va bene.


5
non c'è bisogno di essere timido, benvenuto qui!
Benoit

8
Ritengo che sarebbe sbagliato non collegarsi a stackoverflow.com/questions/1732348/…
Christoffer Hammarström

Grazie a tutti per i commenti utili. Mi scuso per l'XML non formattato correttamente. Ho cancellato alcuni tag per semplificazione.
wrangler

Risposte:


167

Poiché è necessario abbinare il contenuto senza includerlo nel risultato (deve corrispondere name=" ma non fa parte del risultato desiderato) è necessaria una qualche forma di corrispondenza a larghezza zero o cattura di gruppo. Questo può essere fatto facilmente con i seguenti strumenti:

Perl

Con Perl potresti usare l' n opzione per ripetere il ciclo riga per riga e stampare il contenuto di un gruppo di cattura se corrisponde:

perl -ne 'print "$1\n" if /name="(.*?)"/' filename

GNU grep

Se hai una versione migliorata di grep, come GNU grep, potresti avere l' -Popzione disponibile. Questa opzione abiliterà regex simile a Perl, permettendoti di usare \Kuna scorciatoia. Ripristinerà la posizione della corrispondenza, quindi qualsiasi cosa prima che sia di larghezza zero.

grep -Po 'name="\K.*?(?=")' filename

L' o opzione fa in modo che grep stampi solo il testo corrispondente, invece dell'intera riga.

Vim - Editor di testo

Un altro modo è usare direttamente un editor di testo. Con Vim, uno dei vari modi per ottenere ciò sarebbe eliminare le righe senza name=e quindi estrarre il contenuto dalle righe risultanti:

:v/.*name="\v([^"]+).*/d|%s//\1

Grep standard

Se non hai accesso a questi strumenti, per qualche motivo, qualcosa di simile potrebbe essere ottenuto con grep standard. Tuttavia, senza guardarsi intorno, sarà necessaria una pulizia successiva:

grep -o 'name="[^"]*"' filename

Una nota sul salvataggio dei risultati

In tutti i comandi sopra i risultati verranno inviati a stdout. È importante ricordare che puoi sempre salvarli collegandoli a un file aggiungendo:

> result

alla fine del comando.


12
Lookarounds (in GNU grep):grep -Po '.*name="\K.*?(?=".*)'
In pausa fino a nuovo avviso.

@ Dennis Williamson, fantastico. Ho aggiornato la risposta di conseguenza, ma .*ho lasciato entrambi da parte, spero che non ti arrabbi con me. Vorrei chiedere, vedi qualche vantaggio da una partita poco avida su "qualsiasi cosa tranne ""? Non prenderlo come una lotta, sono solo curioso e non sono un esperto di espressioni regolari. Inoltre, il \Ksuggerimento, davvero carino. Grazie Dennis.
Sidyll

2
Perché dovrei essere arrabbiato? Senza il .*, puoi farlo grep -Po '(?<=name=").*?(?=")'. La \Kpuò essere utilizzato per stenografia, ma è davvero necessario solo se la partita alla sua sinistra è di lunghezza variabile. In casi come questo, il motivo per utilizzare i lookaround è abbastanza ovvio. Le operazioni ungreedy sembrano un po 'più ordinate ( [^"]*rispetto a .*?e non è necessario ripetere il personaggio di ancoraggio. Non conosco la velocità. Dipende molto dal contesto, penso. Spero che sia utile.
In pausa fino a nuovo avviso.

@ Dennis Williamson: certamente signore, molte informazioni utili qui. Penso che il motivo per cui ho mantenuto \K(dopo aver fatto ricerche su di esso) e rimosso il .*era lo stesso: farlo sembrare carino (più semplice). E non ho mai pensato di utilizzare al .*?posto del "modo tradizionale" ho imparato da qualche parte. Ma qui non avidi ha davvero senso. Grazie Dennis, i migliori auguri.
Sidyll

+1 per la descrizione del comando. Ti sarei grato se potessi aggiornare la tua risposta per spiegare la parte "[...]" della regex.
lreeder

5

L'espressione regolare sarebbe:

.+name="([^"]+)"

Quindi il raggruppamento sarebbe in \ 1


5

Se stai usando Perl, scarica un modulo per analizzare XML: XML :: Simple , XML :: Twig o XML :: LibXML . Non reinventare la ruota.


3
Nota che l'esempio fornito da OP non è ben formato ( <type="global"per esempio), quindi la maggior parte dei parser XML si lamenta e muore.
bvr

5

A questo scopo dovrebbe essere usato un parser HTML piuttosto che espressioni regolari. Un programma Perl che utilizza HTML::TreeBuilder:

Programma

#!/usr/bin/env perl

use strict;
use warnings;

use HTML::TreeBuilder;

my $tree = HTML::TreeBuilder->new_from_file( \*DATA );
my @elements = $tree->look_down(
    sub { defined $_[0]->attr('name') }
);

for (@elements) {
    print $_->attr('name'), "\n";
}

__DATA__
<table name="content_analyzer" primary-key="id">
  <type="global" />
</table>
<table name="content_analyzer2" primary-key="id">
  <type="global" />
</table>
<table name="content_analyzer_items" primary-key="id">
  <type="global" />
</table>

Produzione

content_analyzer
content_analyzer2
content_analyzer_items

2

questo potrebbe farlo:

perl -ne 'if(m/name="(.*?)"/){ print $1 . "\n"; }'

2

Ecco una soluzione che utilizza HTML tidy e xmlstarlet:

htmlstr='
<table name="content_analyzer" primary-key="id">
<type="global" />
</table>
<table name="content_analyzer2" primary-key="id">
<type="global" />
</table>
<table name="content_analyzer_items" primary-key="id">
<type="global" />
</table>
'

echo "$htmlstr" | tidy -q -c -wrap 0 -numeric -asxml -utf8 --merge-divs yes --merge-spans yes 2>/dev/null |
sed '/type="global"/d' |
xmlstarlet sel -N x="http://www.w3.org/1999/xhtml" -T -t -m "//x:table" -v '@name' -n

1

Oops, il comando sed deve precedere ovviamente il comando tidy:

echo "$htmlstr" | 
sed '/type="global"/d' |
tidy -q -c -wrap 0 -numeric -asxml -utf8 --merge-divs yes --merge-spans yes 2>/dev/null |
xmlstarlet sel -N x="http://www.w3.org/1999/xhtml" -T -t -m "//x:table" -v '@name' -n

0

Se la struttura del tuo xml (o del testo in generale) è fissa, il modo più semplice è usare cut. Per il tuo caso specifico:

echo '<table name="content_analyzer" primary-key="id">
  <type="global" />
</table>
<table name="content_analyzer2" primary-key="id">
  <type="global" />
</table>
<table name="content_analyzer_items" primary-key="id">
  <type="global" />
</table>' | grep name= | cut -f2 -d '"'
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.