Finde alle Vorkommen in einer Datei mit sed

15

Mit OPEN STEP 4.2 OS ... verwende ich derzeit den folgenden sedBefehl:

sed -n '1,/141.299.99.1/p' TESTFILE | tail -3

Dieser Befehl findet eine Instanz in einer Datei mit der IP 141.299.99.1 und enthält auch 3 Zeilen davor, was alles in Ordnung ist, mit der Ausnahme, dass ich auch alle Instanzen der IP und die 3 Zeilen davor finden möchte und nicht nur der erste.

Tal
quelle
1
Bitte geben Sie immer Ihr Betriebssystem an. Lösungen hängen sehr oft vom verwendeten Betriebssystem ab. Verwenden Sie Unix, Linux, BSD, OSX, etwas anderes? Welche Version?
Terdon
GROSSER PUNKT! Die Verwendung von Open Step Version 4.2 ist ziemlich alt und die enthaltenen Shells enthalten nicht viele der in den Antworten unten genannten Funktionen.
Dale
Aus Neugier - Was ist ein OPEN STEP 4.2 System und wofür wird es heute verwendet?
Thorbjørn Ravn Andersen
(und wenn Perl verfügbar ist, können Sie wirklich viele schöne Dinge nur damit tun)
Thorbjørn Ravn Andersen
@ ThorbjørnRavnAndersen Vielleicht ist es das: en.wikipedia.org/wiki/OpenStep
Barmar

Antworten:

4

Hier ist ein Versuch grep -B3, anhand dieses GNU sed-Beispiels ein sed-Moving-Fenster zu emulieren (aber hoffentlich POSIX-konform - mit der Bestätigung an @ StéphaneChazelas):

sed -e '1h;2,4{;H;g;}' -e '1,3d' -e '/141\.299\.99\.1/P' -e '$!N;D' file

Die ersten beiden Ausdrücke bereiten einen mehrzeiligen Musterpuffer vor und ermöglichen es ihm, den Kantenfall zu behandeln, bei dem vor der ersten Übereinstimmung weniger als 3 Zeilen des vorhergehenden Kontexts vorhanden sind. Der mittlere Ausdruck (Regex-Übereinstimmung) druckt eine Linie vom oberen Rand des Fensters, bis der gewünschte Übereinstimmungstext durch den Musterpuffer gewellt ist. Das $!N;DFenster wird um eine Zeile gescrollt, es sei denn, es erreicht das Ende der Eingabe.

Stahlfahrer
quelle
-eist nicht GNU-spezifisch. Um POSIX / portable zu sein, brauchen Sie es, da es nichts danach geben kann }(und Sie brauchen ein ;davor).
Stéphane Chazelas
Danke @ StéphaneChazelas - sagen Sie also, dass die erste Gruppe geteilt / geändert werden muss, um POSIX / portable zu sein -e '1h;2,4{H;g;}' -e '1,3d'? Ich habe kein Nicht-GNU-System zum Testen (und der GNU sed- --posixSchalter scheint es nicht zu interessieren).
Steeldriver
1
Ja, unter Linux können Sie eine andere Implementierung mit sedder Erbstück-Toolchest testen, die vom traditionellen Unix-Sed abstammt. Die POSIX / Unix-Spezifikation für sedist unter pubs.opengroup.org/onlinepubs/9699919799/utilities/sed.html
Stéphane Chazelas
Ich erhalte ein Ereignis, das auf keinem der folgenden Elemente gefunden wurde: N; D ': Ereignis nicht gefunden. Fehlt mir irgendwo die Syntax? Vielen Dank!!
Dale
Es tut mir leid, dass ich gerade festgestellt habe, dass bei meiner letzten Bearbeitung nach dem ersten -e-Ausdruck ein schließendes einfaches Anführungszeichen weggelassen wurde. Ich habe es jetzt korrigiert - können Sie es bitte mit dem obigen Ausdruck noch einmal versuchen?
Steeldriver
10

grep wird dies besser machen:

grep -B 3 141.299.99.1 TESTFILE

Die -B 3Möglichkeit , die drei Zeilen vor jedem Treffer auszudrucken. Dies wird --zwischen jeder Gruppe von Zeilen gedruckt . Um dies zu deaktivieren, verwenden Sie --no-group-separatorebenfalls.

Die -BOption wird von GNUgrep und den meisten BSD-Versionen ( OSX , FreeBSD , OpenBSD , NetBSD ) unterstützt, ist aber technisch keine Standardoption.

Michael Homer
quelle
1
Michael Homer - Danke. Ich habe keine Option - B. Irgendwelche anderen Ideen?
Dale
@Dale Kannst du GNU grep installieren? Das gibt Ihnen die Option.
Barmar
9

Mit sedkönnen Sie ein Schiebefenster machen.

sed '1N;$!N;/141.299.99.1/P;D'

Das tut es. Aber Vorsicht - es ist bashein verrücktes Verhalten, sich zu erweitern, ! selbst wenn es zitiert wird !!! Wenn Sie den Befehlsstring aus Ihrem Befehlsverlauf eingeben, wird er möglicherweise etwas verrückt. Stellen Sie dem Befehl Folgendes voranset +H; wenn dies der Fall ist. Um es dann wieder zu aktivieren (aber warum ???), mache es set -Hdanach.

Das würde natürlich nur zutreffen, wenn Sie es benutzen bash- obwohl ich nicht glaube, dass Sie es sind. Ich bin mir ziemlich sicher, dass Sie damit arbeiten csh- (das ist zufällig die Shell, deren verrücktes Verhalten bashmit der History-Erweiterung nachahmt, aber vielleicht nicht bis zu den Extremen, mit denen die c-Shell es aufgenommen hat) . So wohl einer \!funktionieren. Ich hoffe.

Es ist alles portabler Code: POSIX beschreibt seine drei Operatoren folgendermaßen: (obwohl es erwähnenswert ist, dass ich nur bestätigt habe, dass diese Beschreibung bereits 2001 existiert hat)

[2addr]N Fügen Sie die nächste Eingabezeile abzüglich der abschließenden \newline in den Musterbereich ein. Verwenden Sie dazu eine eingebettete \newline, um das angehängte Material vom Originalmaterial zu trennen. Beachten Sie, dass sich die aktuelle Zeilennummer ändert.

[2addr]P Schreiben Sie den Musterraum bis zur ersten \newline in die Standardausgabe.

[2addr]D Löschen Sie das anfängliche Segment des Musterraums durch die erste \newline und starten Sie den nächsten Zyklus.

In der ersten Zeile fügen Sie dem Musterbereich eine zusätzliche Zeile hinzu. So sieht es aus:

^line 1s contents\nline 2s contents$

Dann fügen Sie in der ersten Zeile und in jeder Zeile danach - mit Ausnahme der allerletzten - eine weitere hinzu Zeile zum Musterraum hinzu. So sieht es also aus:

^line 1\nline 2\nline 3$

Wenn Ihre IP-Adresse in Ihnen Pvorkommt, geben Sie hier einfach Zeile 1 ein. Am Ende eines jeden Zyklus Dwird derselbe Vorgang ausgeführt, und Sie beginnen erneut mit dem, was noch übrig ist. Der nächste Zyklus sieht also so aus:

^line 2\nline 3\nline 4$

...und so weiter. Wenn Ihre IP-Adresse auf einer dieser drei Adressen zu finden ist, wird jedes Mal die älteste ausgedruckt. Also bist du immer nur drei Zeilen vor sich.

Hier ist ein kurzes Beispiel. Für jede Zahl, die mit Null endet, wird ein dreizeiliger Puffer gedruckt:

seq 10 52 | sed '1N;$!N;/0\(\n\|$\)/P;D'

10
18
19
20
28
29
30
38
39
40
48
49
50

Das ist ein bisschen komplizierter als in deinem Fall, weil ich mich von beiden abwechseln musste 0\n Zeilenvorschub oder 0$Ende des Musterraums Ihrem Problem näher zu kommen - aber sie unterscheiden sich geringfügig darin, dass dies einen Anker erfordert - was seitdem etwas schwierig sein kann Der Musterraum verschiebt sich ständig.

Ich habe die ungeraden Fälle 10 und 52 verwendet, um zu zeigen, dass, solange der Anker flexibel ist, auch die Ausgabe flexibel ist. Völlig portabel kann ich dieselben Ergebnisse erzielen, indem ich mich stattdessen auf den Algorithmus verlasse und Folgendes tue:

seq 10 52 | sed '1N;$!N;/[90]\n/P;D'

Und die Suche ausweiten, während ich mein Fenster einschränke - von 0 auf 9 und 0 und von 3 auf zwei Zeilen.

Wie auch immer, Sie haben die Idee.

mikeserv
quelle
Vielen Dank für all Ihre harte Arbeit. Entschuldigung, wo würde ich den Dateinamen eingeben, den ich durchsuchen möchte?
Dale
@ Dale - mein schlechtes. sed '...' $filename. Übrigens - ich habe die Punkte aus Ihrer eigenen Suchzeichenfolge weggelassen, aber das sind eigentlich keine Punkte in einem Muster - diese stehen für ein einzelnes Zeichen. Sie sollten wahrscheinlich tun oct\.oct\.oct\.oct, um ihnen zu entkommen, damit sie nur mit Zeiträumen übereinstimmen.
mikeserv
Ich habe versucht, damit und mit verschiedenen <> Symbolen zu katzen, und es wurde kein Ereignis gefunden, das ich mit anderen Lösungen hier erhalte. Daher frage ich mich, ob mein Betriebssystem nicht mit diesen Lösungen kompatibel ist.
Dale
jetzt ergibt sich mit -> N; /141.299.99.1/P; D ': Ereignis nicht gefunden.
Dale
@ Dale - siehe Update. Es sollte dir helfen.
mikeserv
4

Da Sie erwähnen, dass Sie nicht die -BOption dazu haben grep, können Sie mit Perl (zum Beispiel) ein Fenster mit 4 Zeilen verschieben:

perl -ne '
    push @window,$_;
    shift @window if @window > 4;
    print @window if /141\.299\.99\.1/
' your_file

Rameshs Antwort ist ähnlich awk.

Joseph R.
quelle
Ich bin nicht sicher, ob meine Version von Perl dies unterstützt, aber ich werde es versuchen. Vielen Dank, dass Sie sich die Zeit genommen haben, meine Frage zu beantworten - sehr dankbar!
Dale
@ Dale Gern geschehen. Ich bezweifle, dass dieser Code die neuesten Perl-Funktionen nutzt.
Joseph R.
4

Wenn verfügbar, können Sie pcregrep verwenden :

pcregrep -M '.*\n.*\n.*\n141.299.99.1' file
Chaos
quelle
Ich überprüfe, ob ich PCREGREP habe. Ich mag die Kompaktheit des Befehls. Sehr dankbar für Ihre Zeit und Mühen. Vielen Dank!!!
Dale
4

Sie können den gleichen grundlegenden Ansatz wie die anderen Nicht-Grep-Antworten in der Shell selbst implementieren (dies setzt eine relativ neue Shell voraus, die dies unterstützt =~):

while IFS= read -r line; do 
    [[ $line =~ 141.299.99.1 ]] && printf "%s\n%s\n%s\n%s\n" $a $b $c $line;
    a=$b; b=$c; c=$line; 
done < file 

Alternativ können Sie die gesamte Datei in ein Array schlürfen:

perl -e '@F=<>; 
        for($i=0;$i<=$#F;$i++){
          print $F[$i-3],$F[$i-2],$F[$i-1],$F[$i] if $F[$i]=~/141.299.99.1/
        }' file 
terdon
quelle
Meine Shell ist sehr alt - Steve Jobs Open Step. Tolle Idee und danke für deine Zeit !!! Dale
Dale
@Dale der Perl-Ansatz funktioniert fast überall. Bitte teilen Sie uns Ihr Betriebssystem mit (fügen Sie es Ihrer Frage hinzu), damit wir Ihnen Vorschläge unterbreiten können, die für Sie funktionieren.
Terdon
Wenn ich dein Perl kopiere und in das Notizbuch stecke und es in eine Zeile stecke, funktioniert es! Frage: Wenn ich 10 Zeilen vor dem Übereinstimmungsmuster sagen wollte, wo würde ich die 3 in 10 ändern? Vielen Dank!
Dale
Ich sehe, dass ich durch Hinzufügen weiterer $ F [$ iX] -Anweisungen weitere Zeilen hinzufügen kann. Vielen Dank!
Dale
4

Wenn Ihr System den grepKontext nicht unterstützt , können Sie stattdessen ack-grep versuchen :

ack -B 3 141.299.99.1 file

ack ist ein Tool wie grep, das für Programmierer optimiert wurde.

cuonglm
quelle
Ich mag die Kompaktheit des Befehls, aber mein System unterstützt es nicht, in den Manpages nachzuschlagen. Tolle Idee und vielen Dank für deine Zeit !!! Dale
Dale
@ Dale: Überraschend! Was ist dein Betriebssystem? Wenn Sie haben perl, können Sie verwenden ack.
Cuonglm
2
awk '/141.299.99.1/{for(i=1;i<=x;)print a[i++];print} {for(i=1;i<x;i++)
     a[i]=a[i+1];a[x]=$0;}'  x=3 filename

In dieser awkLösung wird ein Array verwendet, das immer 3 Zeilen vor dem aktuellen Muster enthält. Wenn das Muster übereinstimmt, wird daher der Array-Inhalt zusammen mit dem aktuellen Muster gedruckt.

Testen

-bash-3.2$ cat filename
10.0.0.1
10.0.0.2
10.0.0.3
10.0.0.4
141.299.99.1
10.0.0.5
10.0.0.6
10.0.0.7
10.0.0.8
10.0.0.9
10.0.0.10
141.299.99.1
10.0.0.11
10.0.0.12
10.0.0.13
10.0.0.14
10.0.0.15
10.0.0.16
141.299.99.1
10.0.0.17
10.0.0.18
10.0.0.19

Nachdem ich den Befehl ausgeführt habe, lautet die Ausgabe:

10.0.0.2
10.0.0.3
10.0.0.4
141.299.99.1
10.0.0.8
10.0.0.9
10.0.0.10
141.299.99.1
10.0.0.14
10.0.0.15
10.0.0.16
141.299.99.1
Ramesh
quelle
so detailliert - vielen Dank. Ich werde es versuchen. Sehr dankbar für deine Zeit !! Dale
Dale
Ich habe eine Testdatei und Ihre Lösung funktioniert! Das Problem ist jedoch, wenn ich es in meiner großen Produktionsdatei ausführe, dass es mit einer zu langen Datensatznummer zurückkommt, sodass die Ausgabe nicht mit dem Befehl funktionieren kann. Mein ursprünglicher Befehl oben auf dieser Seite funktioniert, findet jedoch nur eine Instanz. Ich schätze Ihre Hilfe. Kann ich mit meinem ursprünglichen Befehl irgendetwas tun, um mehr als eine Instanz zu finden?
Dale
1

In den meisten Fällen /141.299.99.1/wird auch (zB) 141a299q99+1oder 141029969951weil passen. in einem regulären Ausdruck ein beliebiges Zeichen dargestellt werden kann.

Mit /141[.]299[.]99[.]1/ist sicherer, und Sie können am Anfang zusätzlichen Kontext hinzufügen und den gesamten regulären Ausdrucks paßt zu machen , am Ende sicher nicht 3141., .12, .104etc.

user117529
quelle
1
Dies ist ein guter Punkt - und einer, über den ich auch nachgedacht habe. Trotzdem habe ich die vom Fragesteller bereitgestellte Zeichenfolge als bekanntes Arbeitsmatch verwendet - und ihn bei Gelegenheit persönlich darüber informiert. Die Antwort von steeldriver hat jedenfalls nicht alle von Anfang an das Char Match zitiert.
MikeServ