Zeilenbereich über dem Muster mit sed (oder awk) löschen

28

Ich habe den folgenden Code, der Zeilen mit dem Muster bananaund 2 Zeilen danach entfernt:

sed '/banana/I,+2 d' file

So weit, ist es gut! Ich brauche es, um 2 Zeilen vorher zu entfernen banana, aber ich kann es nicht mit einem "Minuszeichen" oder was auch immer bekommen (ähnlich wie was grep -v -B2 banana filetun soll, aber nicht):

teresaejunior@localhost ~ > LC_ALL=C sed '-2,/banana/I d' file
sed: invalid option -- '2'
teresaejunior@localhost ~ > LC_ALL=C sed '/banana/I,-2 d' file
sed: -e expression #1, char 16: unexpected `,'
teresaejunior@localhost ~ > LC_ALL=C sed '/banana/I,2- d' file
sed: -e expression #1, char 17: unknown command: `-'
Teresa und Junior
quelle
1
Am einfachsten ist es, alle Daten in einem Array zu laden, überspringen die unerwünschten Linien dann ausgegeben was bleibt: awk '{l[m=NR]=$0}/banana/{for(i=NR-2;i<=NR;i++)delete l[i]}END{for(i=1;i<=m;i++)if(i in l)print l[i]}'. Dies ist nicht effizient, daher ist dies nur ein Hinweis, keine Lösung.
Manatwork
6
Tu es einfach tac file | sed ... | tac. : P
Angus
@angus Ich habe nicht darüber nachgedacht;)
Teresa e Junior
1
das hättest sed '/banana/,+2d' file du auch machen können
Akaks
1
Wenn Sie awk verwenden möchten, ist das ganz einfach: awk 'tolower($0)~/bandana/{print prev[!idx];print prev[idx]} {idx=!idx;prev[idx]=$0}' filein Da dies ein Kommentar und keine Antwort ist (es gibt bereits andere Antworten), werde ich nicht zu sehr ins Detail gehen, aber der springende Punkt ist, dass Sie immer die haben vorherige zwei Datensätze in vorh [0] und zurück [1], die „frischeste“ je nachdem , welche Iteration aber immer in prev[idx], also wenn Sie drucken, die Sie in !idxdann idxbestellen. Unabhängig davon, wechseln Sie ab idxund fügen Sie den aktuellen Datensatz ein prev[idx].
Luv2code

Antworten:

22

Sed zieht sich nicht zurück: Sobald eine Zeile verarbeitet wurde, ist sie fertig. "Finde eine Linie und drucke die vorherigen N Linien" funktioniert also nicht so, wie es ist, im Gegensatz zu "Finde eine Linie und drucke die nächsten N Linien", auf die man leicht pfropfen kann.

Wenn die Datei nicht zu lang ist und Sie mit GNU-Erweiterungen einverstanden zu sein scheinen, können Sie sie verwenden tac, um die Zeilen der Datei umzukehren.

tac | sed '/banana/I,+2 d' | tac

Ein weiterer Anstellwinkel besteht darin, ein Schiebefenster in einem Werkzeug wie awk beizubehalten. Anpassen von Gibt es eine Alternative zu den grep-A-B-C-Schaltern (um einige Zeilen davor und danach zu drucken)? (Warnung: minimal getestet):

#!/bin/sh
{ "exec" "awk" "-f" "$0" "$@"; } # -*-awk-*-
# The array h contains the history of lines that are eligible for being "before" lines.
# The variable skip contains the number of lines to skip.
skip { --skip }
match($0, pattern) { skip = before + after }
NR > before && !skip { print NR h[NR-before] }
{ delete h[NR-before]; h[NR] = $0 }
END { if (!skip) {for (i=NR-before+1; i<=NR; i++) print h[i]} }

Verwendung: /path/to/script -v pattern='banana' -v before=2

Gilles 'SO - hör auf böse zu sein'
quelle
2
sedSie können auch Schiebefenster verwenden, aber das resultierende Skript ist in der Regel so unleserlich, dass es einfacher ist, es nur zu verwenden awk.
jw013
@ Gilles .. Das awkSkript ist nicht ganz richtig; wie sie ist druckt leere Zeilen und verfehlt die letzten Zeilen. Dies scheint das Problem zu beheben, aber es ist möglicherweise nicht ideal oder richtig: if (NR-before in h) { print...; delete...; }... und im ENDAbschnitt: for (i in h) print h[i]... Außerdem gibt das awk-Skript die entsprechende Zeile aus, die tac/secVersion jedoch nicht. Aber die Frage ist ein bisschen mehrdeutig. Das "ursprüngliche" awk-Skript, zu dem Sie einen Link bereitgestellt haben, funktioniert einwandfrei. Ich mag es. Ich bin mir nicht sicher, wie sich der obige "Mod" auf den Druck danach auswirkt Zeilen ...
Peter.O
@ Peter.O Danke, das awk-Skript sollte jetzt besser sein. Und ich habe weniger als 6–8 Jahre gebraucht!
Gilles 'SO- hör auf, böse zu sein'
19

Mit ex oder vim -e ist das ganz einfach

    vim -e - $file <<@@@
g/banana/.-2,.d
wq
@@@

Der Ausdruck lautet: Löschen Sie für jede Zeile, die Bananen im Bereich von der aktuellen Zeile -2 bis zur aktuellen Zeile enthält.

Was cool ist, ist, dass der Bereich auch Rückwärts- und Vorwärtssuchen enthalten kann. So werden beispielsweise alle Abschnitte der Datei gelöscht, beginnend mit einer Zeile mit Apfel und endend mit einer Zeile mit Orange und einer Zeile mit Banane:

    vim -e - $file <<@@@
g/banana/?apple?,/orange/d
wq
@@@
Justin Rowe
quelle
7

Verwenden des "Schiebefensters" in perl:

perl -ne 'push @lines, $_;
          splice @lines, 0, 3 if /banana/;
          print shift @lines if @lines > 2
          }{ print @lines;'
Choroba
quelle
6

Sie können dies ziemlich einfach tun mit sed:

printf %s\\n    1 2 3 4match 5match 6 \
                7match 8 9 10 11match |
sed -e'1N;$!N;/\n.*match/!P;D'

Ich weiß nicht, warum jemand etwas anderes sagen würde, aber um eine Linie zu finden und vorherige Linien zu drucken, istsed das integrierte PRint-Primitiv integriert, das nur bis zum ersten \newline-Zeichen im Musterraum schreibt . Das komplementäre DElete-Primitiv entfernt das gleiche Segment des Musterraums, bevor das Skript mit den verbleibenden Elementen rekursiv wiederverwendet wird. Abgerundet wird dies durch Nein \nGrundelement zum Anhängen der ext-Eingabezeile an den Musterraum nach einem eingefügten ewline-Zeichen.

So dass eine Zeile sedalles sein sollte, was Sie brauchen. Sie ersetzen nur matchmit was auch immer Ihre Regexp ist und Sie sind golden. Das sollte auch eine sehr schnelle Lösung sein.

Beachten Sie auch, dass ein matchunmittelbar davorstehender Wert korrekt matchals Auslöser für das Stummschalten der Ausgabe für die vorherigen beiden Zeilen und für das Stummschalten des Drucks gilt:


1
7match
8
11match

Damit es für eine beliebige Anzahl von Zeilen funktioniert , müssen Sie nur einen Vorsprung holen.

So:

    printf %s\\n     1 2 3 4 5 6 7match     \
                     8match 9match 10match  \
                     11match 12 13 14 15 16 \
                     17 18 19 20match       |
    sed -e:b -e'$!{N;2,5bb' -e\} -e'/\n.*match/!P;D'

1
11match
12
13
14
20match

... löscht die 5 Zeilen vor einer Übereinstimmung.

mikeserv
quelle
1

Verwenden von man 1 ed:

str='
1
2
3
banana
4
5
6
banana
8
9
10
'

# using Bash
cat <<-'EOF' | ed -s <(echo "$str")  | sed -e '1{/^$/d;}' -e '2{/^$/d;}'
H
0i


.
,g/banana/km\
'm-2,'md
,p
q
EOF
larz
quelle