Holen Sie sich das letzte Vorkommen eines Musters vor einem anderen Muster

Holen Sie sich das letzte Vorkommen eines Musters vor einem anderen Muster

In einer Datei wie dieser:

...
Pattern2:TheWrongBar
foo 
Pattern2:TheRightBar
foo 
First Pattern
foo
...

Ich muss das letzte Vorkommen davon finden, Pattern2das vor First Patterndem in diesem Fall wärePattern2:TheRightBar

Meine erste Idee besteht darin, alle verbleibenden Dateien vorher abzurufen First patternmit:

sed -e '/First Pattern/,$d' myfile | tac | grep -m1 "Pattern I need to get"

Gibt es keine Möglichkeit, diesen Code zu optimieren?

Antwort1

Mit awk:

awk '/Pattern2/ {line=$0; next}; /First Pattern/ {print line; exit}' file.txt
  • /Pattern2/ {line=$0; next}: Wenn das Muster Pattern2übereinstimmt, wird die Zeile in der Variablen gespeichert lineund zur nächsten Zeile gewechselt

  • /First Pattern/ {print line; exit}: wenn First Patterngefunden, die Variable drucken lineund beenden

Beispiel:

% cat file.txt                                                                 
...
Pattern2:TheWrongBar
foo 
Pattern2:TheRightBar
foo 
First Pattern
foo
...

% awk '/Pattern2/ {line=$0; next}; /First Pattern/ {print line; exit}' file.txt
Pattern2:TheRightBar

Antwort2

Du könntest rennen

sed '/PATTERN2/h;/PATTERN1/!d;x;/PATTERN2/!d;q' infile

Wie es funktioniert:

sed '/PATTERN2/h         # if line matches PATTERN2 save it to hold buffer 
/PATTERN1/!d             # if it doesn't match PATTERN1 delete it
x                        # exchange buffers
/PATTERN2/!d             # if current pattern space doesn't match delete it
q' infile                # quit (auto-printing the current pattern space)

Dies würde nur beendet, wenn es mindestens eine Zeilenübereinstimmung PATTERN2vor einer Zeilenübereinstimmung gibt PATTERN1, also mit einer Eingabe wie

1
2
PATTERN1
PATTERN2--1st
3
PATTERN2--2nd
PATTERN1
...

es wird gedruckt

PATTERN2--2nd

Wenn Sie stattdessen beim ersten Treffer oder unabhängig davon beenden möchten PATTERN1, führen Sie aus

sed -n '/PATTERN2/h;/PATTERN1/!d;x;/PATTERN2/p;q' infile

das mit der obigen Eingabe nichts ausgibt (dieses macht genau das, was Ihre Lösung macht).

Antwort3

Findet die Zeilenanzahl des „ersten Musters“, verwendet dann head, um die darüber liegenden Zeilen anzuzeigen, leitet durch tac und grepst es.

head --lines=+"$(grep -nm1 "First Pattern" file | cut -d\: -f1)" file | tac | grep -m1 "Pattern2" 

Z.B.

head --lines=+6 file | tac | grep -m1 "Pattern2" 

Dies ist zuverlässiger als die Verwendung von -m 1000000 in grep. Da Geschwindigkeit für OP wichtig ist, habe ich die Laufzeit überprüft und sie scheint auch schneller zu sein als alle anderen aktuellen Antworten (auf meinem System).

wc -l file
25910209 file

time awk '/Pattern2/ {line=$0; next}; /First Pattern/ {print line; exit}' file
Pattern2:TheRightBar

real  0m2.881s
user  0m2.844s
sys 0m0.036s

time sed '/Pattern2/h;/First Pattern/!d;x;/Pattern2/!d;q' file
Pattern2:TheRightBar

real  0m5.218s
user  0m5.192s
sys 0m0.024s

time (grep -m1 "First Pattern" file -B 10000000 | tac | grep -m1 "Pattern2")

real  0m0.624s
user  0m0.552s
sys 0m0.124s

time (head --lines=+"$(grep -nm1 "First Pattern" file | cut -d\: -f1)" file | tac | grep -m1 "Pattern2")
Pattern2:TheRightBar

real  0m0.586s
user  0m0.528s
sys 0m0.160s

Antwort4

Der effizienteste Weg istin meinem FallWar:

grep -m1 "First Pattern" my_file -B 10000000 | tac | grep -m1 "Pattern2"

Offensichtlich kann die -BOption in einigen Beispielen nicht verwendet werden, grepist aber so viel schneller als awkoder sed, dass ich mich für diese Lösung entschieden habe. Wenn der Wert für die Option -Bhöher wird, ist die Suche viel weniger effizient.

verwandte Informationen