So extrahieren Sie Zeichenfolgen nach einem Muster mit grep, regex oder perl

89

Ich habe eine Datei, die ungefähr so ​​aussieht:

    <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>

Ich muss alles in den folgenden Anführungszeichen extrahieren name=, dh content_analyzer, content_analyzer2und content_analyzer_items.

Ich mache das auf einer Linux-Box, also ist eine Lösung mit sed, perl, grep oder bash in Ordnung.

Wrangler
quelle
5
Sie müssen nicht schüchtern sein, willkommen hier!
Benoit
8
Ich denke
Christoffer Hammarström
Vielen Dank an alle für die nützlichen Kommentare. Ich entschuldige mich dafür, dass das XML nicht richtig formatiert wurde. Ich habe einige Tags zur Vereinfachung gelöscht.
Wrangler

Antworten:

161

Da Sie Inhalte name=" abgleichen müssen, ohne sie in das Ergebnis aufzunehmen (müssen übereinstimmen, aber nicht Teil des gewünschten Ergebnisses sein), ist eine Form der Übereinstimmung mit der Breite Null oder der Gruppenerfassung erforderlich. Dies kann einfach mit den folgenden Tools durchgeführt werden:

Perl

Mit Perl können Sie die n Option verwenden, um Zeile für Zeile eine Schleife zu erstellen und den Inhalt einer Erfassungsgruppe zu drucken, wenn er übereinstimmt:

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

GNU grep

Wenn Sie eine verbesserte Version von grep haben, z. B. GNU grep, steht Ihnen möglicherweise die -POption zur Verfügung. Diese Option aktiviert Perl-ähnliche Regex, sodass Sie \Keine Kurzform verwenden können. Die Übereinstimmungsposition wird zurückgesetzt, also alles, was vorher Null ist.

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

Mit dieser o Option druckt grep nur den übereinstimmenden Text anstelle der gesamten Zeile.

Vim - Texteditor

Eine andere Möglichkeit besteht darin, einen Texteditor direkt zu verwenden. Mit Vim besteht eine der verschiedenen Möglichkeiten, dies zu erreichen, darin, Zeilen ohne zu löschen name=und dann den Inhalt aus den resultierenden Zeilen zu extrahieren:

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

Standard grep

Wenn Sie aus irgendeinem Grund keinen Zugriff auf diese Tools haben, kann mit Standard-Grep etwas Ähnliches erreicht werden. Ohne das Umschauen muss es jedoch später bereinigt werden:

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

Ein Hinweis zum Speichern von Ergebnissen

In allen obigen Befehlen werden die Ergebnisse an gesendet stdout. Es ist wichtig zu beachten, dass Sie sie jederzeit speichern können, indem Sie sie durch Anhängen an eine Datei weiterleiten:

> result

bis zum Ende des Befehls.

Sidyll
quelle
11
Lookarounds (in GNU grep):grep -Po '.*name="\K.*?(?=".*)'
Bis auf weiteres angehalten.
@ Tennis Williamson, großartig. Ich habe die Antwort entsprechend aktualisiert, aber beide .*beiseite gelassen . Ich hoffe, Sie werden mir nicht böse. Ich würde gerne fragen, sehen Sie irgendwelche Vorteile von einem gierigen Match gegenüber "irgendetwas außer ""? Nimm das nicht als Kampf, ich bin nur neugierig und kein Regex-Experte. Auch der \KTipp, wirklich schön. Danke Dennis.
Sidyll
2
Warum sollte ich wütend sein? Ohne das .*kannst du tun grep -Po '(?<=name=").*?(?=")'. Das \Kkann für Kurzschrift verwendet werden, wird aber wirklich nur benötigt, wenn die Übereinstimmung links davon eine variable Länge hat. In solchen Fällen liegt der Grund für die Verwendung von Lookarounds auf der Hand. Ungreedy Operationen schauen ein wenig übersichtliche ( im [^"]*Vergleich zu .*?und Sie nicht über die Ankerzeichen wiederholen müssen ich nicht wissen , über Geschwindigkeit , die viel hängt vom Kontext ab, ich denke , ich hoffe , das ist hilfreich....
Pausieren bis auf weiteres.
@ Tennis Williamson: sicherlich Sir, viele hilfreiche Informationen hier. Ich denke, der Grund, warum ich das behalten habe \K(nachdem ich es recherchiert habe) und es entfernt habe, .*war der gleiche: Lass es hübsch aussehen (einfacher). Und ich habe nie daran gedacht, .*?anstelle der "traditionellen Art", die ich irgendwo gelernt habe, zu verwenden. Aber hier macht es wirklich Sinn, nicht gierig zu sein. Danke Dennis, beste Wünsche.
Sidyll
+1 zur Beschreibung des Befehls. Würde mich freuen, wenn Sie Ihre Antwort aktualisieren könnten, um den [...] Teil der Regex zu erklären.
lreeder
5

Der reguläre Ausdruck wäre:

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

Dann wäre die Gruppierung in der \ 1

Matt Shaver
quelle
5

Wenn Sie Perl verwenden, laden Sie ein Modul herunter, um XML zu analysieren: XML :: Simple , XML :: Twig oder XML :: LibXML . Erfinde das Rad nicht neu.

shawnhcorey
quelle
3
Beachten Sie, dass das von OP angegebene Beispiel ( <type="global"zum Beispiel) nicht gut geformt ist , sodass sich die meisten XML-Parser nur beschweren und sterben.
bvr
5

Zu diesem Zweck sollte ein HTML-Parser anstelle von regulären Ausdrücken verwendet werden. Ein Perl-Programm, das Folgendes verwendet HTML::TreeBuilder:

Programm

#!/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>

Ausgabe

content_analyzer
content_analyzer2
content_analyzer_items
Alan Haggai Alavi
quelle
2

das könnte es tun:

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

Hier ist eine Lösung mit HTML tidy & 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
Mitma
quelle
1

Hoppla, der Befehl sed muss natürlich dem Befehl ordentlich vorausgehen:

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
Mitma
quelle
0

Wenn die Struktur Ihrer XML-Datei (oder des Textes im Allgemeinen) festgelegt ist, ist die Verwendung am einfachsten cut. Für Ihren speziellen Fall:

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 '"'
Carlos Lindado
quelle