2017-03-08 2 views
0

Ich studiere Perl. Meine data.txt Datei enthält:Perl Regex verwenden, um Felder mit mehreren Trennzeichen zu vergleichen

Lori:James Apple 
Jamie:Eric Orange 

Mein Code unten druckt die erste Zeile „Lori: James Apfel“

open(FILE,'data.txt'); 
while(<FILE>){ 
    print if /James/; 
} 

Aber wie kann ich meine regulären Ausdruck für ein bestimmtes Feld suchen ändern? Zum Beispiel möchte ich 2 Trennzeichen '' und ':' verwenden, damit jede Zeile drei Felder enthält und überprüft, ob das dritte Feld der ersten Zeile Apple ist. Was äquivalent sein awk -F'[ :]' '$3 = "Lori"' data.txt

Antwort

4

Eine einfache Möglichkeit, mit regex ist die negated character class (auch in perlreftut sehen es)

open my $fh, '<', $file or die "Can't open $file: $!"; 

while (my $line = <$fh>) 
{ 
    my @fields = $line =~ /([^:\s]+)/g; 
} 

Die [^...] Spiele zu verwenden alle Zeichen andere als die im Inneren aufgelistet (nach ^ welche „negiert“). Die +quantifier bedeutet, dass ein oder mehrere Male übereinstimmen, so dass das gesamte Muster einer Zeichenfolge aus anderen aufeinander folgenden Zeichen als : und "Leerraum" entspricht. Eine genaue Beschreibung von \s finden Sie in der Dokumentation. Wenn Sie tatsächlich nur einen einzelnen Literalbereich überspringen möchten, verwenden Sie [^: ]. All dies ist erfasst von ().

Die Suche wird durch die Zeichenfolge aufgrund der globalen Modifikator/g gehen, finden Sie alle solche Übereinstimmungen. Da es in der Liste context ist, gibt es die Liste der Übereinstimmungen zurück, die @fields Array zugeordnet ist.

Elemente können "on the fly" durch Indexierung in die Liste ($line =~ /([^:\s]+)/g)[2] ausgewählt werden. Wenn wir passen $_ ist dies (/([^:\s]+)/g)[2].

Ich schlage eine gute Lektüre durch perlreftut, für den Anfang.


Auf der anderen Seite ist es oft einfacher und klarer split

my @fields = split /[:\s]/, $line; 

Dies auch regex, durch die für das Muster der Zeichenfolge zu spalten verwendet zu verwenden. Die Zeichenklasse wird nicht negiert, da sie hier das Trennzeichen selbst angibt, entweder : oder \s (jedes Trennzeichen kann eines davon sein, sie müssen nicht alle gleich sein).


Ich würde jetzt gerne die spezifische Frage beantworten, aber die Frage ist mir nicht klar.

Es fragt nach „zu überprüfen, ob das dritte Feld der ersten Zeile ist Apple-“, was zum Beispiel getan werden kann, durch

while (<$fh>) 
{ 
    if ((/([^:\s]+)/g)[2] eq 'Apple') { 
     # .... 
    } 
} 

aber es ist nicht klar, was damit zu tun. Vielleicht bekommst du das erste Feld von dem was das dritte ist?

Ich schlage vor, ein Array zu bekommen und dann zu verarbeiten.Man kann eine Regex schreiben, um Felder direkt zu identifizieren und auszuwählen, aber das ist spröder und die Regex selbst hängt dann von der Position (und Anzahl) von Feldern ab.

An diesem Punkt sind wir in einem Ratespiel. Wenn Sie mehr Details benötigen, erklären Sie bitte.

Der angegebene awk Code würde Lori James Lori ergeben und ich sehe nicht, wie das passt.

+0

Für die spezifische Frage "wie man die Regex ändert" nehme ich an, die Antwort könnte etwas wie '/^([^:] * [:]) {2} James ($ | [:]) /' – tripleee

+0

sein @tripleee Ja, aber ich sehe nicht, was die spezifische Frage ist (die angebotenen Beispiele sind widersprüchlich). Ich habe einen weiteren Abschnitt hinzugefügt ... aber ich fühle, dass ich zu diesem Zeitpunkt in einem Ratespiel bin. (Btw, das muss '{1}' Ich denke? Oder 'Apple' anstelle von' James'.) – zdim

+0

'{1}' ist sinnlos; Wenn etwas nicht wiederholt wird, geben Sie einfach keine Anzahl von Wiederholungen an (sonst würden wir sagen:/J {1} a {1} m {1} e {1} s {1}/'). Ich überspringe die ersten zwei Felder und das folgende Trennzeichen, deshalb ist es zwei Wiederholungen. Aber ja, suchen Sie wahrscheinlich im dritten Feld nach "Apple" oder zielen Sie stattdessen auf das zweite Feld. – tripleee

0

Die kurze Antwort ist - nicht. Reguläre Ausdrücke beziehen sich auf Mustervergleiche und nicht auf Kontext.

Sie können definieren Sie ein Muster, das in Begrenzer und Felder erstellt, aber ... es ist nicht das richtige Werkzeug für den Job.

Die Antwort lautet split verwenden und dann die Felder getrennt behandeln.

open (my $input, '<', 'data.txt') or die $!; 
while(<$input>){ 
    chomp; 
    my @fields = split /[\s:]/; 
    print if $fields[2] eq "Apple"; 
} 

Sie können dieses kompakte weiter, wenn Sie wollen, aber ich würde Vorsicht raten - Code auf Kosten der Lesbarkeit Komprimierung ist keine Tugend.

auch - während wir gerade dabei sind:

open(FILE,'data.txt'); 

ist schlechter Stil - es sucht nicht nach Erfolg, und es nutzt auch einen globalen Namen Datei-Handle. Es wäre viel besser:

open (my $input, '<', 'data.txt') or die $!; 

Die autodie Pragma auch tut dies implizit.

Verwandte Themen