Verwenden Sie sed, um alle Zeilen zwischen zwei übereinstimmenden Mustern zu löschen


76

Ich habe eine Datei wie:

# ID 1
blah blah
blah blah
$ description 1
blah blah
# ID 2
blah
$ description 2
blah blah
blah blah

Wie kann ich mit einem sed-Befehl alle Zeilen zwischen der #und -Zeile löschen $? Das Ergebnis wird also:

# ID 1
$ description 1
blah blah
# ID 2
$ description 2
blah blah
blah blah

Können Sie bitte auch eine Erklärung geben?

Antworten:


72

Verwenden Sie diesen sed-Befehl, um Folgendes zu erreichen:

sed '/^#/,/^\$/{/^#/!{/^\$/!d}}' file.txt

Mac-Benutzer müssen (um extra characters at the end of d commandFehler zu vermeiden ) vor den schließenden Klammern Semikolons hinzufügen

sed '/^#/,/^\$/{/^#/!{/^\$/!d;};}' file.txt

AUSGABE

# ID 1
$ description 1
blah blah
# ID 2
$ description 2
blah blah
blah blah

Erläuterung:

  • /^#/,/^\$/wird den gesamten Text zwischen Zeilen, die mit beginnen #, und Zeilen, die mit beginnen , abgleichen $. ^wird für den Zeilenanfang verwendet. $ist ein Sonderzeichen und muss daher entkommen.
  • /^#/! bedeutet folgendes tun, wenn der Zeilenanfang nicht ist #
  • /^$/! bedeutet folgendes tun, wenn der Zeilenanfang nicht ist $
  • d bedeutet löschen

Insgesamt werden also zuerst alle Zeilen von ^#bis ^\$dann von diesen übereinstimmenden Zeilen abgeglichen, um Linien zu finden, die nicht übereinstimmen ^# und nicht übereinstimmen, ^\$ und sie mit zu löschen d.


14
Für Mac-Benutzer: Um extra characters at the end of d commandFehler zu vermeiden , müssen Sie vor den schließenden Klammern Semikolons hinzufügensed '/^#/,/^\$/{/^#/!{/^\$/!d;};}' file.txt
AvL

Downvoted als Lösung von @sleport ist prägnanter.
Neil

2
Ist das der Grund für eine Ablehnung? Wenn Ihnen eine Antwort gefällt, stimmen Sie sie ab. Downvote ist normalerweise für eine Antwort gedacht, die das Problem von OP nicht löst. Da OP diese Antwort akzeptiert hat, bedeutet dies, dass es für OP funktioniert hat. Ist es nicht?
Anubhava

Wie würden Sie das tun, wenn Sie die Zeilen # und $ zum Löschen einfügen möchten? Wenn Sie $ am Ende einer Zeile finden möchten, können Sie $ \ $,
Timo

Dann benutzen Sie einfach:sed '/^#/,/^\$/d' file
anubhava

48
$ cat test
1
start
2
end
3
$ sed -n '1,/start/p;/end/,$p' test
1
start
end
3
$ sed '/start/,/end/d' test
1
3

2
Die Geschwindigkeit, mit der dies bei 300-MB-Dateien funktioniert, ist beeindruckend. Ich spreche auf einer SSD im Bruchteil einer Sekunde.
Ray Foss

Ich war ein wenig verwirrt, da ich mit der sed-Syntax nicht vertraut bin. Es war nicht klar, dass der erste und der zweite sed-Befehl keine Abhängigkeit hatten - dh der Unterschied zwischen den beiden besteht darin, ob Sie das Match-Token beibehalten möchten oder nicht. Bis ich es getestet habe, war ich davon ausgegangen, dass der erste Befehl alles zwischen den Token entfernt und der zweite die Token selbst entfernt hat. Wenn Sie versuchen, einen Block zwischen Token zu entfernen, müssen Sie nur den zweiten Befehl verwenden.
Lukevp

Keine Ahnung warum, aber '1,/start/p;/end/,$p'ich habe meinen Workflow komplett durcheinander gebracht, da ich mich auf diese Arbeit verlassen habe. Bei mir funktioniert das überhaupt nicht.
Akito

github.com/theAkito/akito-libbash/blob/… ist die fragliche Zeile. Habe ich etwas verpasst? Für mich sieht es so aus, als ob es genau so ist, wie Sie es in Ihrer Antwort @Lri gezeigt haben.
Akito

1
Die Lösung, die tatsächlich funktioniert, ist die folgende:sed '/PATTERN-1/,/PATTERN-2/{//!d}' input.txt
Akito

16

Wenn Sie im Allgemeinen eine Datei mit dem Inhalt des Formulars abcde haben , wobei Abschnitt a vor Muster b steht , Abschnitt c vor Muster d steht , dann Abschnitt e folgt und Sie die folgenden sedBefehle anwenden , erhalten Sie die folgenden Ergebnisse.

In dieser Demonstration wird die Ausgabe durch dargestellt => abcde, wobei die Buchstaben zeigen, welche Abschnitte in der Ausgabe enthalten wären. Somit aezeigt eine Ausgabe nur der Abschnitte a und e , acewären die Abschnitte a , c und e usw.

Beachten Sie, dass wenn boder din der Ausgabe erscheinen, dies die Muster sind, die erscheinen (dh sie werden so behandelt, als wären sie Abschnitte in der Ausgabe).

Verwechseln Sie das /d/Muster auch nicht mit dem Befehl d. Der Befehl ist bei diesen Demonstrationen immer am Ende. Das Muster liegt immer zwischen dem //.

  • sed -n -e '/b/,/d/!p' abcde => ae
  • sed -n -e '/b/,/d/p' abcde => bcd
  • sed -n -e '/b/,/d/{//!p}' abcde => c
  • sed -n -e '/b/,/d/{//p}' abcde => bd
  • sed -e '/b/,/d/!d' abcde => bcd
  • sed -e '/b/,/d/d' abcde => ae
  • sed -e '/b/,/d/{//!d}' abcde => abde
  • sed -e '/b/,/d/{//d}' abcde => Ass

10

Ein anderer Ansatz mit sed:

sed '/^#/,/^\$/{//!d;};' file
  • /^#/,/^\$/: von der Zeile beginnend mit #bis zur nächsten Zeile beginnend mit$
  • //!d: Löschen Sie alle Zeilen mit Ausnahme derjenigen, die den Adressmustern entsprechen

1
Wie machst du das einschließlich der Muster?
Qodeninja

2
Versuchen Sie Folgendes : sed '/^#/,/^\$/d;' file.
SLePort

4

Ich habe so etwas vor langer Zeit gemacht und es war so etwas wie:

sed -n -e "1,/# ID 1/ p" -e "/\$ description 1/,$ p"

Welches ist so etwas wie:

  • -n Alle Ausgaben unterdrücken
  • -e "1,/# ID 1/ p" Ausführen von der ersten Zeile bis zu Ihrem Muster und p (Drucken)
  • -e "/\$ description 1/,$ p" Ausführen vom zweiten Muster bis zum Ende und p (Drucken).

Ich könnte mich irren, wenn einige der Saiten entkommen, also überprüfen Sie es bitte noch einmal.


0

Im folgenden Beispiel werden Zeilen zwischen "if" und "end if" entfernt .

Alle Dateien werden gescannt und Linien zwischen den beiden übereinstimmenden Mustern werden entfernt (einschließlich dieser).

IFS='
'
PATTERN_1="^if"
PATTERN_2="end if"

# Search for the 1st pattern in all files under the current directory.
GREP_RESULTS=(`grep -nRi "$PATTERN_1" .`)

# Go through each result
for line in "${GREP_RESULTS[@]}"; do

   # Save the file and line number where the match was found.
   FILE=${line%%:*}
   START_LINE=`echo "$line" | cut -f2 -d:`

   # Search on the same file for a match of the 2nd pattern. The search 
   # starts from the line where the 1st pattern was matched.
   GREP_RESULT=(`tail -n +${START_LINE} $FILE | grep -in "$PATTERN_2" | head -n1`)
   END_LINE="$(( $START_LINE + `echo "$GREP_RESULT" | cut -f1 -d:` - 1 ))"

   # Remove lines between first and second match from file
   sed -e "${START_LINE},${END_LINE}d;" $FILE > $FILE

done
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.