Grepping-String, aber alle nicht leeren Zeilen nach jedem Grep-Match einschließen


7

Betrachten Sie das folgende Spielzeugbeispiel:

this is a line 
this line contains FOO 
this line is not blank

This line also contains FOO

Some random text

This line contains FOO too
Not blank 
Also not blank

More random text 
FOO!
Yet more random text
FOO!

Ich möchte also die Ergebnisse eines Grep für FOO, aber mit der zusätzlichen Falte, dass Linien, die den übereinstimmenden Linien folgen, enthalten sein sollten, solange sie nicht leer sind und selbst kein FOO enthalten. Die Übereinstimmungen würden also wie folgt aussehen, wobei die verschiedenen Übereinstimmungen getrennt wären:

MATCH 1

this line contains FOO 
this line is not blank

MATCH 2

This line also contains FOO

MATCH 3

This line contains FOO too 
Not blank 
Also not blank

MATCH 4

FOO!
Yet more random text

MATCH 5

FOO!

Bonuspunkte (bildlich gesprochen) für ein einfaches einzeiliges Skript, das über die Befehlszeile ausgeführt werden kann.

ADDENDUM: Das Hinzufügen einer laufenden Anzahl der Übereinstimmungsnummern wäre sehr praktisch, wenn es nicht zu schwierig ist.


2
Möchten Sie alle Übereinstimmungen in einer Ausgabe oder möchten Sie, dass sie separat ausgeführt werden?
Vivek Kanadiya

Ich denke, eine Ausgabe ist in Ordnung, solange sie klar voneinander getrennt sind, beispielsweise durch eine leere Zeile.
Faheem Mitha

Möglicherweise nicht in einer Ausgabe möglich, aber ich kann versuchen, in jeder einzelnen Ausgabe zu sehen!
Vivek Kanadiya

Für die Übereinstimmungen 4 und 5 könnte die einfache Logik "Ich habe (das 4.)" FOO "gefunden, daher sollte ich in diesem Übereinstimmungsblock weiter drucken, bis ich eine leere Zeile sehe, und daher ist das endgültige" FOO "in enthalten dieser Block ", aber ich sehe, dass Sie ihn getrennt haben - weil es ein separates" FOO "-Match ist. Müssen Sie das 5. "FOO" separat drucken?
Jeff Schaller

@ JeffSchaller Ja, bitte trennen Sie verschiedene FOO-Übereinstimmungen.
Faheem Mitha

Antworten:


9

Verwenden awkstatt grep:

awk '/FOO/ { if (matching) printf("\n"); matching = 1 }
     /^$/  { if (matching) printf("\n"); matching = 0 }
     matching' file

Eine Version, die die Übereinstimmungen auflistet:

awk 'function flush_print_maybe() {
         if (matching) printf("Match %d\n%s\n\n", ++n, buf)
         buf = ""
     }
     /FOO/ { flush_print_maybe(); matching = 1 }
     /^$/  { flush_print_maybe(); matching = 0 }
     matching { buf = (buf == "" ? $0 : buf ORS $0) }
     END   { flush_print_maybe() }' file

Beide awkProgramme verwenden eine sehr einfache "Zustandsmaschine", um festzustellen, ob sie aktuell übereinstimmt oder nicht. Eine Übereinstimmung des Musters FOOführt dazu, dass es in den matchingStatus wechselt, und eine Übereinstimmung des Musters ^$(eine leere Zeile) bewirkt, dass es in den Nicht- matchingStatus wechselt.

Die Ausgabe von Leerzeilen zwischen übereinstimmenden Datensätzen erfolgt bei Zustandsübergängen von matching (entweder in matchingoder in Nicht- matching).

Das erste Programm druckt eine beliebige Zeile im matchingStatus.

Das zweite Programm sammelt Zeilen in einer bufVariablen, wenn es sich in einem matchingZustand befindet. Dies wird geleert (geleert), nachdem es möglicherweise gedruckt wurde (abhängig vom Status), zusammen mit einem Match NEtikett bei Statusübergängen (wenn das erste Programm eine leere Zeile ausgeben würde).

Ausgabe dieses letzten Programms auf die Beispieldaten:

Match 1
this line contains FOO
this line is not blank

Match 2
This line also contains FOO

Match 3
This line contains FOO too
Not blank
Also not blank

Match 4
FOO!
Yet more random text

Match 5
FOO!

Nitpicking, diese geben eine zusätzliche leere Zeile aus, wenn am Ende der Eingabe ein nicht übereinstimmender Abschnitt steht ;-).
Stephen Kitt

@ StephenKitt Ja. Es wird. Jeder Ergebnisblock wird durch eine leere Zeile abgeschlossen.
Kusalananda

Nur wenn nach dem Ergebnisblock eine leere Zeile steht (der Beispieltext in der Frage bewirkt also nicht, dass am Ende eine leere Zeile gedruckt wird).
Stephen Kitt

@ StephenKitt Es ist fast Mitternacht hier, ich werde später darauf zurückkommen. :-)
Kusalananda

6
sed -ne '/FOO/{x;P;x};/FOO/,/^$/p' testfile

Jeder Block nicht leerer Zeilen in der Ausgabe ist ein einzelner Block übereinstimmender Daten von der Eingabe. Die Anzahl der Zeilenumbrüche variiert.

Diese

  1. unterdrückt output ( -n); dann
  2. druckt vor jedem Auftreten von "FOO" eine leere Zeile ( /FOO/{x;P;x}- verwendet den leeren Haltebereich);
  3. wählt Zeilenbereiche aus, die mit FOO ( /FOO/) beginnen und in leeren Zeilen ( /^$/) enden ; und schlussendlich
  4. druckt diese Zeilen ( p).

this line contains FOO
this line is not blank


This line also contains FOO


This line contains FOO too
Not blank
Also not blank


FOO!
Yet more random text

FOO!

5

Ich denke nicht, dass dies mit machbar ist grep, aber es ist mit AWK:

#! /usr/bin/awk -f

/FOO/ {
  matched = 1
  if (notfirst) print ""
  notfirst = 1
}

/^$/ {
  matched = 0
}

matched

Mit einer Anzahl von Übereinstimmungen:

#! /usr/bin/awk -f

/FOO/ {
  matched = 1
  if (matches) print ""
  printf "Match %d\n", ++matches
}

/^$/ {
  matched = 0
}

matched

In beiden Fällen bestimmen die ersten beiden Blöcke, ob der aktuelle Datensatz in die Ausgabe kopiert werden soll. Wenn der aktuelle Datensatz mit „FOO“ übereinstimmt, wird der erste Block matchedauf 1 gesetzt und gibt bei Bedarf einen leeren Datensatz aus (um die bevorstehende Ausgabe von der vorherigen Übereinstimmung zu trennen). In der zweiten Variante wird auch der matchesZähler erhöht und ein Header ausgegeben. Wenn der aktuelle Datensatz leer ist, wird der zweite Block matchedauf 0 gesetzt. Der matchedZustand " Einsam" gibt den aktuellen Datensatz aus, wenn er matched1 ist.


2

Ich habe eine pcregrepLösung und eine pythonLösung aufgenommen.

Multiline Grep-Lösung

Wenn Sie pcregrepinstalliert haben, können Sie ein mehrzeiliges Muster verwenden, z ^.*FOO.*$\n?(^.*\S.*$\n?)*. B.:

pcregrep -M '^.*FOO.*$\n?(^.*\S.*$\n?)*' test.txt

Der Unterausdruck stimmt ^.*FOO.*$\n?mit jeder Zeile überein, die die Zeichenfolge enthält, FOOund der Unterausdruck stimmt (^.*\S.*$\n?)*mit einer beliebigen Anzahl nachfolgender Zeilen überein, die ein Nicht-Leerzeichen enthalten.

Python-Lösung

Hier ist ein Python-Skript, das tun soll, was Sie wollen:

#!/usr/bin/env python3
# -*- encoding: utf8 -*-
"""grep_follow.py

Search a text file for a pattern,
and output that pattern and the
non-empty lines which immediately follow it.
"""

import re
import sys

# Get the search pattern and the input file as command-line arguments
pattern = sys.argv[1]
input_file = sys.argv[2]

# Set a flag to determine whether or not to output the current line
print_flag = False

with open(input_file, "r") as _input_file:

    # Iterate over the lines of the input file
    for line in _input_file:

        # Remove trailing whitespace
        line = line.rstrip()

        # If the line is empty, stop producing output
        if not line.strip():
            print_flag = False

        # If the line matches the search pattern, start producing output
        elif re.search(pattern, line):
            print_flag = True

        # If the print flag is set then output the line
        if print_flag:
            print(line)

Sie würden es so laufen lassen:

$ python grep_follow.py FOO test.txt
this line contains FOO
this line is not blank
This line also contains FOO
This line contains FOO too
Not blank
Also not blank
FOO!
Yet more random text
FOO!

1
Ein intuitiverer Regex in den Augen dieses Betrachters ist '(?s)^\N*FOO.*?(?=\Z|\n\n|\N*FOO\N*)', aber +1
iruvar

1
awk '/FOO/{print "===match " ++i "==="} /FOO/,/^$/' file

===match 1===
this line contains FOO
this line is not blank

===match 2===
This line also contains FOO

===match 3===
This line contains FOO too
Not blank
Also not blank

===match 4===
FOO!
Yet more random text
===match 5===
FOO!

Eine ähnliche Variante, bei FOOder leicht etwas anderes geändert werden könnte:

awk -vpat=FOO '$0~pat{print "===match " ++i "==="} $0~pat,/^$/' file

Das Weglassen der abschließenden Leerzeile aus dem Standarddruck bleibt dem Leser als Übung überlassen ;-)


1

Eine Möglichkeit kann die Verwendung des Perl'sBereichsoperators sein ...:

$ perl -lne '
     print s|^(?=.*FOO)|$/."MATCH#".++$c.$/|re if /FOO/ ... /^$/;
 ' input.txt

MATCH#1
this line contains FOO 
this line is not blank


MATCH#2
This line also contains FOO


MATCH#3
This line contains FOO too
Not blank 
Also not blank


MATCH#4
FOO!
Yet more random text

MATCH#5
FOO!

Falls die zusätzlichen Leerzeilen ein Problem darstellen, können Sie den folgenden Code verwenden, wobei Sie den Bereichsoperator im skalaren Kontext verwenden, um einen Blick in die Werte der Zustandsmaschine zu werfen und darauf basierende Aktionen auszuführen.

Hinweis :

$/ = RS = "\ n"

$, = OFS = ""

$ perl -lne '
    print
      s{^(?=.*FOO)}
       {
         ($,,$/)[$r>1] . 
         "MATCH#" . ++$count . $/
       }rex 
          if $r = /FOO/ ... /^$/;
' input.txt

Hier ist eine andere Methode, um die Pbm anzugehen, diesmal mit der paragraph modevon Perl. Wir lesen jeweils einen Absatz ein und teilen sie in Zeilenumbrüchen in Felder auf.

Dann stellen wir sicher, dass nur die Paras behandelt werden, in denen sich der String Foo befindet. Schließlich drucken wir die Felder mit der Einschränkung, dass foo-haltige Felder speziell behandelt werden. Auch hier behandeln wir das erste Feld anders als die anderen.

$ perl -aln -F'\n' -00 -e '
     next unless /FOO/;
     shift @F until $F[0] =~ /FOO/;
     push @F, $, if ! eof;
     print "MATCH#", ++$k, $\, shift @F;
     print for map { /FOO/ and $_ = "\nMATCH#" . ++$k . "\n$_";$_ } @F;
' input.txt 

0

Hier ist ein Einzeiler, der möglicherweise nicht genau den Kriterien entspricht, aber für die meisten Zwecke nahe genug kommt.

gawk '/FOO/{p=1} {if(p) print} /^$/{p=0}' infile.txt

Erste Regelsätze, p=1wenn FOO angezeigt wird. Die zweite Regel gibt die aktuelle Zeile aus, wenn p nicht Null ist. Die dritte Regel wird festgelegt, p=0wenn die Zeile (bereits gedruckt) leer ist. Tauschen Sie die zweite und dritte Regel aus, wenn der Terminator für leere Zeilen nicht ausgegeben werden soll.

Dies ist eine Variante, die die Übereinstimmungen nummeriert und trennt, wenn mehrere FOO-Instanzen vorhanden sind, die nicht durch Leerzeilen getrennt sind

gawk '/FOO/{p=1; n++; print "\n" n} /^$/{p=0} //{if (p) print $0 }' infile.txt
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.