2012-10-16 13 views
9

Brauchen Sie Hilfe beim Scannen von Textdateien und finden Sie alle Wörter zwischen zwei Mustern. Wie gesagt, wenn wir eine .sql-Datei haben, müssen Sie alle Wörter zwischen 'und' wo 'suchen und finden. Grep kann nur 1 Zeile gleichzeitig scannen. Für diese Anforderung Welches ist das beste Unix-Skript? sed, awk hat diese Eigenschaften? Auf Beispiele zu verweisen wird sehr geschätzt.Grep Access Mehrere Zeilen, finde alle Wörter zwischen zwei Mustern

+1

können Sie einen Beispiel-SQL-Inhalt einfügen? z.B. wie viele von ... wo in deiner Datei? Gibt es den Fall, dass "von" und "wo" in derselben Zeile stehen? All dies macht die Extraktionslogik anders. – Kent

+0

Diese Antwort könnte auch zutreffen: https://Stackoverflow.com/a/48022994/2026975 – imriss

Antwort

21

Sed hat dies:

sed -n -e '/from/,/where/ p' file.sql 

Drucke alle Linien zwischen einer Linie mit einem from und eine Zeile mit einem where.

Für etwas, das Linien, die beide aus und wo können sein: Diese

#!/bin/sed -nf 

/from.*where/ { 
    s/.*\(from.*where\).*/\1/p 
    d 
} 
/from/ { 
    : next 
    N 
    /where/ { 
     s/^[^\n]*\(from.*where\)[^\n]*/\1/p 
     d 
    } 
    $! b next 
} 

(geschrieben als sed-Skript) ist etwas komplexer, und ich werde versuchen, um die Details zu erklären. Die erste Zeile wird in einer Zeile ausgeführt, die sowohl from als auch where enthält. Wenn eine Zeile mit diesem Muster übereinstimmt, werden zwei Befehle ausgeführt. Wir verwenden den Ersetzungsbefehl s, um nur die Teile zwischen from und wo zu extrahieren (einschließlich from und wo). Das Suffix in diesem Befehl gibt die Zeile aus. Der Löschbefehl löscht den Musterbereich (den Arbeitspuffer), lädt die nächste Zeile und startet das Skript neu.

Der zweite Befehl startet die Ausführung einer Reihe von Befehlen (gruppiert nach geschweiften Klammern), wenn eine Zeile gefunden wird, die from enthält. Im Grunde bilden die Befehle eine Schleife, die Zeilen von der Eingabe an den Musterbereich anfügt, bis eine Zeile mit where gefunden wird oder bis wir die letzte Zeile erreichen.

Der Befehl ":" erstellt eine Beschriftung, eine Markierung im Skript, die es uns ermöglicht, "zurückzuspringen", wann immer wir wollen. Der Befehl N liest eine Zeile aus der Eingabe und hängt sie an den Musterbereich an (die Zeilen werden durch ein Zeilenvorschubzeichen getrennt).

Wenn ein where gefunden wird, können wir den Inhalt des Musterbereichs drucken, aber zuerst müssen wir ihn mit dem Ersatzbefehl reinigen. Es ist analog zu dem, das vorher verwendet wurde, aber wir ersetzen nun das führende und nachfolgende .* durch [^\n]*, was sed sagt, dass nur Nicht-Newline-Zeichen übereinstimmen, was effektiv einem von in der ersten Zeile und einem wo in der letzten Zeile entspricht. Der Befehl d löscht dann den Musterbereich und startet das Skript in der nächsten Zeile neu.

Der Befehl b springt auf ein Etikett, in unserem Fall auf das Etikett next. Die Adresse $! besagt jedoch, dass sie in der letzten Zeile nicht ausgeführt werden darf, damit wir die Schleife verlassen können. Wenn Sie die Schleife auf diese Weise verlassen, haben wir keine entsprechende where gefunden, so dass Sie sie möglicherweise nicht drucken möchten.

Beachten Sie jedoch, dass dies einige Nachteile hat. Die folgenden Fälle werden nicht wie erwartet behandelt:

from ... where ... from 

from ... from 
where 

from 
where ... where 

from 
from 
where 
where 

Die Verarbeitung dieser Fälle erfordert mehr Code.

Hope this =)

+0

unkompliziert, aber ich denke nicht, dass es was OP braucht .... – Kent

+0

Danke, rettete mir einen Haufen RTFM'ing :-) –

+0

eine Idee für die Zeilennummer wird auch am Anfang der Zeile mit der übereinstimmenden Zeile gedruckt, wenn das Muster übereinstimmt –

2

Mit GNU awk hilft so können Sie die RS auf ein RE gesetzt:

gawk -v RS='[[:space:]]+' ' 
    /where/ { found=0 } 
    found { print } 
    /from/ { found=1 } 
' file 

Die obige Sie die "von" nicht davon ausgegangen, wollen und "where" gedruckt, Verschieben Sie die Linien falls nötig, um etwas anderes zu tun.

Falls es hilft, beschreiben die folgenden Idiome, wie eine Reihe von Aufzeichnungen ein bestimmtes Muster gegeben auszuwählen übereinstimmen:

von einem Muster

a) Drucken Sie alle Datensätze:

awk '/pattern/{f=1}f' file 

b) drucken Sie alle Aufzeichnungen nach einigen Mustern:

awk 'f;/pattern/{f=1}' file 

c) Drucke des N-ten Datensatz nach einigen Mustern:

Druck

awk 'c&&c--;/pattern/{c=N}' file 

f) jeden Datensatz mit Ausnahme der:

awk 'c&&!--c;/pattern/{c=N}' file 

d) Drucke jeder Datensatz außer dem N-ten Datensatz nach einigen Mustern:

awk 'c&&!--c{next}/pattern/{c=N}1' file 

e) Drucken Sie die N Aufzeichnungen nach einigen Mustern N Datensätze nach einem Muster:

awk 'c&&c--{next}/pattern/{c=N}1' file 

g) Drucken Sie das N reco rds von einem Muster:

awk '/pattern/{c=N}c&&c--' file 

ich die Variablennamen aus „f“ geändert für „gefunden“ zu „c“ für „count“, wo angemessen wie ausdruck von dem, was die Variable tatsächlich ist.

1

Sie könnten hierfür ed verwenden, dies ermöglicht positive und negative Offsets für den Regex-Bereich. Wenn die Eingabe:

seq 10 | tee > infile 
1 
2 
3 
4 
5 
6 
7 
8 
9 
10 

Rohr im Befehl ed:

<<< /3/,/6/p | ed -s infile 

d.h. alles zwischen den Linien gedruckt enthalten 3 und 6.

Ergebnis:

3 
4 
5 
6 

Um an jedem Ende eine weitere Zeile zu erhalten:

<<< /3/-1,/5/+1p | ed -s infile 

Ergebnis:

2 
3 
4 
5 
6 
7 

Oder umgekehrt:

<<< /3/+1,/6/-1p | ed -s infile 

Ergebnis:

4 
5 
0

Um nur einen String innerhalb von zwei bestimmten Strings, entlang der Linien von awk zurückkehren (ohne verrückt zu bekommen) Ich laufe gerade dieses sehr flach Skript, Ausführlichkeit im Schlepptau:

.\gnucoreutils\bin\awk "{startstring = \"RETURN STUFF AFTER ME \"; endstring = \"RETURN STUFF BEFORE ME\"; endofstartstring = index($0,startstring)+length(startstring); print substr($0,endofstartstring,index($0,endstring)-endofstartstring)}" /dev/stdin 

beachten Sie, dass ich bin mit cmd.exe (der Befehlsinterpreter mit Windows) und the gnuwin32 awk, so beachten Sie die "doppelte Anführungszeichen" und^\ Escape-Zeichen^\:

GNU Awk 3.1.6 
Copyright (C) 1989, 1991-2007 Free Software Foundation. 

Bitte weisen Sie auf Fehler hin.

Beispiel:

echo "hello. RETURN STUFF AFTER ME i get returned RETURN STUFF BEFORE ME my face is melting" | .\gnucoreutils\bin\awk "{startstring = \"RETURN STUFF AFTER ME \"; endstring = \" RETURN STUFF BEFORE ME\"; endofstartstring = index($0,startstring)+length(startstring); print substr($0,endofstartstring,index($0,endstring)-endofstartstring)}" /dev/stdin 
i get returned 
1

Ich konnte dies mit nur grep erreichen:

#> grep -A#### "start pattern" file | grep -B#### "end pattern" 

Das Problem war, dass ich die richtige Menge an Linien zu finden, hatte in der A und B umfassen Optionen, die gleich sind. Hoffe, das hilft

Verwandte Themen