Wie bekomme ich alle Linien zwischen dem ersten und dem letzten Auftreten von Mustern?

8

Wie kann ich eine Datei (gut eingegebener Stream) so zuschneiden, dass nur die Zeilen vom ersten foobis zum letzten Auftreten des Musters angezeigt werden bar?

Betrachten Sie zum Beispiel die folgende Eingabe:

A line
like
foo
this 
foo
bar
something
something else
foo
bar
and
the
rest

Ich erwarte diese Ausgabe:

foo
this 
foo
bar
something
something else
foo
bar
Rahmu
quelle
3
Single Pass Stream oder eine Datei? Dies ist viel einfacher, wenn ein wahlfreier Zugriff zulässig ist. Mit einer Datei würden Sie nur die erste foound die letzte finden barund alles dazwischen drucken, wenn überhaupt. Bei einem Stream müssten Sie bis zum ersten lesen foound alle nachfolgenden Zeilen im Speicher bis zum EOF puffern, wobei der Puffer jedes Mal geleert wird, wenn a angezeigt barwird. Dies kann bedeuten, dass der gesamte Stream im Speicher gepuffert wird.
jw013

Antworten:

6
sed -n '/foo/{:a;N;/^\n/s/^\n//;/bar/{p;s/.*//;};ba};'

Die sed-Musterübereinstimmung /first/,/second/liest Zeilen nacheinander. Wenn eine Linie mit ihr übereinstimmt, /first/merkt sie sich diese und freut sich auf die erste Übereinstimmung für das /second/Muster. Gleichzeitig werden alle für dieses Muster angegebenen Aktivitäten angewendet. Danach beginnt der Vorgang immer wieder bis zum Ende der Datei.

Das brauchen wir nicht. Wir müssen bis zum letzten Musterabgleich nachschlagen /second/. Deshalb bauen wir eine Konstruktion, die nur nach dem ersten Eintrag sucht /foo/. Wenn gefunden, abeginnt der Zyklus . Wir fügen dem Übereinstimmungspuffer eine neue Zeile hinzu Nund prüfen, ob sie mit dem Muster übereinstimmt /bar/. Wenn dies der Fall ist, drucken wir es einfach aus und löschen den Übereinstimmungspuffer. Springen Sie mit janyway zum Beginn des Zyklus mit ba.

Außerdem müssen wir das Zeilenumbruchsymbol nach der Pufferbereinigung mit löschen /^\n/s/^\n//. Ich bin mir sicher, dass es eine viel bessere Lösung gibt, die mir leider nicht in den Sinn gekommen ist.

Hoffe alles ist klar.

eilen
quelle
1
Es klappt! Es wäre super cool, wenn Sie uns durch die Konstruktion eines solchen Befehls führen könnten. Ich würde mich dumm fühlen,
wenn
1
Entschuldigung, ich habe die Erklärung nicht mit der Antwort gepostet. Jetzt ist es in der Post.
Eile
In einigen sedBSD zB Versionen sed (was auf Macs gefunden hat), Tags durch eine neue Zeile oder Ende der Zeichenkette gefolgt werden muß, so dass die folgenden zwicken notwendig sind: sed -n -e '/foo/{:a' -e 'N;/^\n/s/^\n//;/bar/{p;s/.*//;};ba' -e '};' Das auch funktioniert auf GNU sed, so dass ich diese Modifikation (multiple denke -eargs Das Beenden eines Arg nach jedem Zweignamen ist eine gute tragbare Angewohnheit, wenn Sie Zweige in sed verwenden.
Wildcard
4

Ich würde es mit einem kleinen Perl-Einzeiler machen.

cat <<EOF | perl -ne 'BEGIN { $/ = undef; } print $1 if(/(foo.*bar)/s)'
A line
like
foo
this 
foo
bar
something
something else
foo
bar
and
the
rest
EOF

ergibt

foo
this 
foo
bar
something
something else
foo
bar
user1146332
quelle
3
Wenn dies Code-Golf wäre, könnten Sie Eanstelle von eund -00777anstelle des $/Bits verwenden (siehe Perlrun (1)). Was es verkürzen würde auf: perl -0777 -nE 'say /(foo.*bar)/s'immer noch irgendwie lesbar.
Thor
1
Ich wusste nichts über diese Flaggen! Ich bin mir sicher, dass besonders -0[octal]das in meinem Workflow seinen Weg finden wird! Vielen Dank dafür
user1146332
3

Hier ist eine GNU-Lösung mit zwei Durchgängen, die nicht viel Speicher benötigt:

< infile                                     \
| sed -n '/foo/ { =; :a; z; N; /bar/=; ba }' \
| sed -n '1p; $p'                            \
| tr '\n' ' '                                \
| sed 's/ /,/; s/ /p/'                       \
| sed -n -f - infile

Erläuterung

  • Der erste sedAufruf übergibt infile und findet das erste Auftreten foound alle nachfolgenden Vorkommen von bar.
  • Diese Adressen werden dann in ein neues sedSkript mit zwei Aufrufen von sedund einem geformt tr. Die Ausgabe des dritten sederfolgt [start_address],[end_address]pohne die Klammern.
  • Der endgültige Aufruf von sedPässen wird infileerneut ausgeführt, wobei die gefundenen Adressen und alles dazwischen gedruckt werden.
Thor
quelle
2

Wenn die Eingabedatei bequem in den Speicher passt, halten Sie es einfach .

Wenn die Eingabedatei sehr groß ist, können Sie csplitsie beim ersten foound bei jedem weiteren barAufteilen in Teile zerlegen und dann zusammenfügen. Die Stücke werden genannt piece-000000000, piece-000000001usw. ein Präfix Wählen Sie (hier piece-) , die mit anderen vorhandenen Dateien nicht kollidieren wird.

csplit -f piece- -n 9 - '%foo%' '/bar/' '{*}' <input-file

(Auf Nicht-Linux-Systemen müssen Sie z. B. eine große Zahl in geschweiften Klammern verwenden {999999999}und die -kOption übergeben. Diese Zahl ist die Anzahl der barTeile.)

Sie können alle Teile mit zusammenbauen cat piece-*, aber dies gibt Ihnen alles nach dem ersten foo. Entfernen Sie also zuerst das letzte Stück. Da die von erstellten Dateinamen csplitkeine Sonderzeichen enthalten, können Sie sie ohne besondere Anführungszeichen für Zitate bearbeiten, z. B. mit

rm $(echo piece-* | sed 's/.* //')

oder äquivalent

rm $(ls piece-* | tail -n 1)

Jetzt können Sie alle Teile verbinden und die temporären Dateien entfernen:

cat piece-* >output
rm piece-*

Wenn Sie die verketteten Teile entfernen möchten, um Speicherplatz zu sparen, führen Sie dies in einer Schleife aus:

mv piece-000000000 output
for x in piece-?????????; do
  cat "$x" >>output; rm "$x"
done
Gilles 'SO - hör auf böse zu sein'
quelle
1

Hier ist ein anderer Weg mit sed:

sed '/foo/,$!d;H;/bar/!d;s/.*//;x;s/\n//' infile

Jede Zeile im /foo/,$Bereich (Zeilen, die !nicht in diesem Bereich liegen, wird dentfernt) wird an den Halten Platz angehängt . Nicht übereinstimmende Zeilen barwerden dann gelöscht. Bei übereinstimmenden Zeilen wird der Musterbereich geleert, xmit dem Haltebereich geändert und die führende leere Zeile im Musterbereich entfernt.

Bei großen Eingaben und wenigen Vorkommen barsollte dies (viel) schneller sein, als jede Zeile in den Musterraum zu ziehen und dann jedes Mal den Musterraum auf zu überprüfen bar.
Erklärt:

sed '/foo/,$!d                     # delete line if not in this range
H                                  # append to hold space
/bar/!d                            # if it doesn't match bar, delete 
s/.*//                             # otherwise empty pattern space and
x                                  # exchange hold buffer w. pattern space then
s/\n//                             # remove the leading newline
' infile

Sicher, wenn dies eine Datei ist (und in den Speicher passt), können Sie einfach Folgendes ausführen:

 ed -s infile<<'IN'
.t.
/foo/,?bar?p
q
IN

weil ed kann vorwärts und rückwärts suchen .
Sie können sogar eine Befehlsausgabe in den Textpuffer lesen, wenn Ihre Shell die Prozessersetzung unterstützt:

printf '%s\n' .t. /foo/,?bar?p q | ed -s <(your command)

oder wenn nicht, mit gnu ed:

printf '%s\n' .t. /foo/,?bar?p q | ed -s '!your command'
don_crissti
quelle
0

Verwenden eines beliebigen awk in einer beliebigen Shell auf einem beliebigen UNIX-System und ohne gleichzeitiges Lesen der gesamten Datei oder des Eingabestreams in den Speicher:

$ awk '
    f {
        rec = rec $0 ORS
        if (/bar/) {
            printf "%s", rec
            rec = ""
        }
        next
    }
    /foo/ { f=1; rec=$0 ORS }
' file
foo
this
foo
bar
something
something else
foo
bar
Ed Morton
quelle
0

Grep könnte es auch tun (na ja, GNU grep):

<infile grep -ozP '(?s)foo.*bar' | tr '\0' '\n'

<infile grep -ozP '        #  call grep to print only the matching section (`-o`)
                           #  use NUL for delimiter (`-z`) (read the whole file).
                           #  And using pcre regex.
(?s)foo.*bar               #  Allow the dot (`.`) to also match newlines.
' | tr '\0' '\n'           #  Restore the NULs to newlines.

Für die Eingabe aus dem Fragetext:

$ <infile grep -ozP '(?s)foo.*bar' | tr '\0' '\n'
foo
this 
foo
bar
something
something else
foo
bar
Isaac
quelle