2017-11-17 3 views
0

Ich versuche, Daten zu analysieren, wie:Parsing-String in mehrzeiligen Daten mit positivem Lookbehind

header1 
------- 
var1 0 
var2 5 
var3 9 
var6 1 

header2 
------- 
var1 -3 
var3 5 
var5 0 

Jetzt mag ich zum Beispiel bekommen var3 für header2. Was ist der beste Weg, dies zu tun?

Bisher war ich meine Dateien line-by-line-Parsing über

open(FILE,"< $file"); 
while (my $line = <FILE>){ 
    # do stuff 
} 

aber ich denke, es ist nicht möglich mehrzeilige Parsen richtig zu handhaben.

Jetzt denke ich die Datei auf einmal zu analysieren, war aber bisher nicht erfolgreich ...

my @Input; 
open(FILE,"< $file"); 
while (<FILE>){ @Input = <FILE>; } 
if (@Input =~ /header2/){ 
    #... 
} 
+1

"* aber ich denke, es ist nicht möglich, Multiline-Parsing richtig zu behandeln *" - Wenn Sie mehr von Ihrem tatsächlichen Code zeigen, könnte dieses Problem vielleicht gelöst werden? Es gibt wahrscheinlich eine einfache Lösung, ohne komplizierte Regexes verwenden zu müssen. "Jetzt denke ich daran, die Datei sofort zu analysieren, war aber bisher nicht erfolgreich ... *" - Können Sie erklären, wie das für Sie nicht funktioniert? In diesem Snippet gibt es eine Reihe von Problemen, die jedoch nicht mit dem Problem in Ihrem Titel zusammenhängen (Regexes mit Lookbehind). Siehe auch https://perlmaven.com/slurp für wertvolle Hinweise. – amon

Antwort

2
while (<FILE>){ @Input = <FILE>; } 

Das macht nicht viel Sinn machen. "Während Sie einen Datensatz aus FILE lesen können, lesen Sie alle Daten von FILE in @Input". Ich denke, was Sie wirklich wollen, ist einfach:

my @Input = <FILE>; 
if (@Input =~ /header2/){ 

Auch dies recht seltsam. Der Bindungsoperator (=~) erwartet skalare Operanden, also wertet er beide Operanden im skalaren Kontext aus. Das heißt, @Input wird als Anzahl der Elemente in @Input ausgewertet. Das ist eine Ganzzahl und wird niemals "header2" entsprechen.

Ein paar Ansätze. Erstens ein Regex-Ansatz.

#!/usr/bin/perl 

use strict; 
use warnings; 
use feature 'say'; 

my $file = 'file'; 

open my $fh, '<', $file or die $!; 

my $data = join '', <$fh>; 

if ($data =~ /header2.+var3 (.+?)\n/s) { 
    say $1; 
} else { 
    say 'Not found'; 
} 

Der Schlüssel dazu ist die /s auf dem m// Betreiber. Ohne es werden die zwei Punkte in der Regex Newline nicht übereinstimmen.

Der andere Ansatz ist eher eine Zeile für Zeile Parser.

#!/usr/bin/perl 

use strict; 
use warnings; 
use feature 'say'; 

my $file = 'file'; 

open my $fh, '<', $file or die $!; 

my $section = ''; 

while (<$fh>) { 
    chomp; 
    # if the line all word characters, 
    # then we've got a section header. 
    if ($_ !~ /\W/) { 
    $section = $_; 
    next; 
    } 

    my ($key, $val) = split; 
    if ($section eq 'header2' and $key eq 'var3') { 
    say $val; 
    last; 
    } 
} 

Wir lesen die Datei Zeile für Zeile und notieren die Abschnittsüberschriften. Bei Datenzeilen teilen wir Leerzeichen auf und prüfen, ob wir im richtigen Abschnitt sind und den richtigen Schlüssel haben.

In beiden Fällen habe ich zu einem Standardansatz (lexikalische Dateihandles, 3-Arg open(), or die $!) zum Öffnen der Datei gewechselt.

+0

Ich habe auch versucht, diese Regex-Ansatz mit '/ s 'vor, aber dachte, die'.' Würde "so viel wie möglich" zwischen den beiden Keywords übereinstimmen. Wenn ich nach header1 - var2 suchen würde, würde es header2 - var2 wieder entsprechen? Oder ist das nicht der Fall? – EverythingRightPlace

+0

Sie haben Recht. Es funktioniert nur zufällig - weil 'header2' der letzte Header in Ihrer Datei ist. Meine zweite Lösung funktioniert trotzdem. –

+0

Mit 'header1 (?:. +?) Var2' funktioniert es wie ein Zauber. – EverythingRightPlace

3

Der einfachere Weg, dies zu handhaben, ist "Absatzmodus".

local $/ = ""; 
while (<>) { 
    my ($header, $body) =~ /^([^\n]*)\n-+\n(.*)/s 
     or die("Bad data"); 

    my @data = map [ split ], split /\n/, $body; 

    # ... Do something with $header and @data ... 
} 

Das gleiche kann, ohne mit $/ Unordnung wie folgt erreicht werden:

my @buf; 
while (1) { 
    my $line = <>; 
    $line =~ s/\s+\z// if !defined($line); 
    if (!length($line)) { 
     if (@buf) { 
      my $header = shift(@buf); 
      shift(@buf); 
      my @data = map [ split ], splice(@buf); 

      # ... Do something with $header and @data ... 
     } 

     last if !defined($line); 
     next; 
    } 

    push @buf, $line; 
} 

(In der Tat enthält das zweite Schnipsel ein paar kleine Verbesserungen gegenüber dem ersten.)

Schnell Kommentare zu Ihrem Versuch:

  • Die while Schleife weil @Input = <FILE> Orte in @Input die restlichen Zeilen der Datei unbrauchbar ist.
  • @Input =~ /header2/ passt header2 gegen die Stringifizierung des Arrays, die die Zeichenfolge der Anzahl der Elemente in @Input ist. Wenn Sie ein Element von @Input enthält header2 überprüfen möchten, müssen Sie die Elemente von @Inputs Schleife durchlaufen und überprüfen Sie sie einzeln.