Ich habe einen Text in einer Textdatei, in dem ich alles zwischen den Zeichenketten \{{[}
und {]}\}
löschen möchte - einschließlich dieser Zeichenketten selbst. Diese beiden Zeichenkettendürfenliegen sowohl auf verschiedenen als auch auf derselben Linie. In beiden Fällen Ander Zeile, in der der Anfang \{{[}
liegt, möchte ich nicht, dass der Text davor, also links, gelöscht wird - und das gleiche gilt für den Text danach {]}\}
.
Hier ein Beispiel: Gegeben sei eine Textdatei mit dem Inhalt
Bla Bla bla bla \{{[} more bla bla
even more bla bla bla bla.
A lot of stuff might be here.
Bla bla {]}\} finally done.
Nonetheless, the \{{[} show {]}\} goes on.
Das Skript sollte eine weitere Textdatei mit dem Inhalt zurückgeben
Bla Bla bla bla finally done.
Nonetheless, the goes on.
Leider erwies sich diese scheinbar einfache Aufgabe als zu schwierig für mich sed
. Ich bin zufrieden mitbeliebigLösung in jeder Sprache, solange ich nichts auf meinem Standard-Linux-Rechner installieren muss (C und etwas Java sind bereits installiert).
Antwort1
Mit perl
:
perl -0777 -pe 's/\Q\{{[}\E.*?\Q{]}\}\E//gs'
Beachten Sie, dass die gesamte Eingabe vor der Verarbeitung in den Speicher geladen wird.
\Qsomething\E
soll something
als Literalzeichenfolge und nicht als regulärer Ausdruck behandelt werden.
Um eine normale Datei direkt zu ändern, fügen Sie die -i
Option hinzu:
perl -0777 -i -pe 's/\Q\{{[}\E.*?\Q{]}\}\E//gs' file.txt
Mit GNU awk
oder mawk
:
awk -v 'RS=\\\\\\{\\{\\[}|\\{\\]}\\\\}' -v ORS= NR%2
Dort definieren wir dieDatensatztrennzeichenals einer dieser Anfangs- oder Endmarker (nur gawk
und mawk
unterstützt RS
hier einen regulären Ausdruck). Aber wir müssen die Zeichen maskieren, die der reguläre Ausdrucksoperator sind (Backslash, {
, [
) und auch den Backslash noch einmal, weil er in Argumenten speziell ist (wird für Dinge wie , ... -v
verwendet ), daher die zahlreichen Backslashes.\n
\b
Dann müssen wir nur noch jeden anderen Datensatz ausdrucken. NR%2
wäre 1
(wahr) für jeden ungeraden Datensatz.
Bei beiden Lösungen gehen wir davon aus, dass die Markierungen übereinstimmen und diese Abschnitte nicht verschachtelt sind.
Um die Datei direkt zu ändern, fügen Sie mit neueren Versionen von GNU awk
die -i /usr/share/awk/inplace.awk
Option ¹ hinzu.
¹verwende nicht-i inplace
as versucht zunächst, die Erweiterung (as oder ) aus dem aktuellen Arbeitsverzeichnis gawk
zu laden , wo jemand Malware platziert haben könnte. Der Pfad der mit gelieferten Erweiterung kann je nach System unterschiedlich sein, siehe die Ausgabe voninplace
inplace
inplace.awk
inplace
gawk
gawk 'BEGIN{print ENVIRON["AWKPATH"]}'
Antwort2
sed -e:t -e'y/\n/ /;/\\{{\[}/!b' \
-e:N -e'/\\{{\[.*{\]}\\}/!N' \
-e's/\(\\{{\[}\).*\n/\1/;tN' \
-e'y/ /\n/;s/\\{{\[}/& /;ts' \
-e:s -e's/\(\[} [^ ]*\)\({\]}\\}\)/\1 \2/' \
-ets -e's/..... [^ ]* .....//;s/ //g;bt' \
<<""
#Bla Bla {]}\} bla bla \{{[} more bla bla
#even more bla bla bla bla. \{{[}
#
#A lot of stuff might be here.
#hashes are for stupid syntax color only
#Bla bla {]}\} finally {]}\} done.
#
#Nonetheless, the \{{[} show {]}\} goes \{{[} show {]}\} on.
#Bla Bla {]}\} bla bla finally {]}\} done.
#
#Nonetheless, the goes on.
Es gibt jedoch eine viel bessere Methode. Es gibt weitaus weniger Ersetzungen, und die, die vorgenommen werden, beziehen sich auf ein paar Zeichen gleichzeitig und nicht .*
immer. Praktisch .*
wird nur dann Ersetzungen verwendet, wenn der Musterraum zwischen den Leerzeichen gelöscht wird, wenn der erste vorkommende Anfang definitiv mit dem ersten folgenden Ende gepaart ist. In der übrigen Zeit wird sed
einfach D
so viel gelöscht wie nötig, um zum nächsten vorkommenden Trennzeichen zu gelangen. Don hat mir das beigebracht.
sed -etD -e:t -e'/\\{{\[}/!b' \
-e's//\n /;h;D' -e:D \
-e'/^}/{H;x;s/\n.*\n.//;}' \
-ett -e's/{\]}\\}/\n}/' \
-e'/\n/!{$!N;s//& /;}' -eD \
<<""
#Bla Bla {]}\} bla bla \{{[} more bla bla
#even more bla bla bla bla. \{{[}
#
#A lot of stuff might be here.
#hashes are for stupid syntax color only
#Bla bla {]}\} finally {]}\} done.
#
#Nonetheless, the \{{[} show {]}\} goes \{{[} show {]}\} on.
#Bla Bla {]}\} bla bla finally {]}\} done.
#
#Nonetheless, the goes on.
Die \n
Ewline-Escapezeichen auf der rechten Seite müssen allerdings möglicherweise durch wörtliche, mit Backslash versehene Escape-Newline-Zeichen ersetzt werden.
Hier ist eine allgemeinere Version:
#!/usr/bin/sed -f
####replace everything between START and END
#branch to :Kil if a successful substitution
#has already occurred. this can only happen
#if pattern space has been Deleted earlier
t Kil
#set a Ret :label so we can come back here
#when we've cleared a START -> END occurrence
#and check for another if need be
:Ret
#if no START, don't
/START/!b
#sigh. there is one. get to work. replace it
#with a newline followed by an S and save
#a copy then Delete up to our S marker.
s||\
S|
h;D
#set the :Kil label. we'll come back here from now
#on until we've definitely got END at the head of
#pattern space.
:Kil
#do we?
/^E/{
#if so, we'll append it to our earlier save
#and slice out everything between the two newlines
#we've managed to insert at just the right points
H;x
s|\nS.*\nE||
}
#if we did just clear START -> END we should
#branch back to :Ret and look for another START
t Ret
#pattern space didnt start w/ END, but is there even
#one at all? if so replace it w/ a newline followed
#by an E so we'll recognize it at the next :Kil
s|END|\
E|
#if that last was successful we'll have a newline
#but if not it means we need to get the next line
#if the last line we've got unmatched pairs and are
#currently in a delete cycle anyway, but maybe we
#should print up to our START marker in that case?
/\n/!{
#i guess so. now that i'm thinking about it
#we'll swap into hold space, and Print it
${ x;P;d
}
#get next input line and add S after the delimiting
#newline because we're still in START state. Delete
#will handle everything up to our marker before we
#branch back to :Kil at the top of the script
N
s||&S|
}
#now Delete will slice everything from head of pattern space
#to the first occurring newline and loop back to top of script.
#because we've definitely made successful substitutions if we
#have a newline at all we'll test true and branch to :Kil
#to go again until we've definitely got ^E
D
...ohne Kommentare...
#!/usr/bin/sed -f
t Kil
:Ret
/START/!b
s||\
S|
h;D
:Kil
/^E/{
H;x
s|\nS.*\nE||
}
t Ret
s|END|\
E|
/\n/!{
${ x;P;d
}
N
s||&S|
}
D
Ich habe die kommentierte Version in meine Zwischenablage kopiert und Folgendes getan:
{ xsel; echo; } >se.sed
chmod +x se.sed
./se.sed <se.sed
#!/usr/bin/sed -f
####replace everything between
#branch to :Kil if a successful substitution
#has already occurred. this can only happen
#if pattern space has been Deleted earlier
t Kil
#set a Ret :label so we can come back here
#when we've cleared a occurrence
#and check for another if need be
:Ret
#if no at the head of
#pattern space.
:Kil
#do we?
/^E/{
#if so, we'll append it to our earlier save
#and slice out everything between the two newlines
#we've managed to insert at just the right points
H;x
s|\nS.*\nE||
}
#if we did just clear we should
#branch back to :Ret and look for another , but is there even
#one at all? if so replace it w/ a newline followed
#by an E so we'll recognize it at the next :Kil
s|END|\
E|
#if that last was successful we'll have a newline
#but if not it means we need to get the next line
#if the last line we've got unmatched pairs and are
#currently in a delete cycle anyway, but maybe we
#should print up to our
Antwort3
Wenn Ihre Datei test.txt ist, können Sie Folgendes verwenden:
sed ':a;N;$!ba;s/\n/ /g' test.txt|sed 's/\\{{\[}.*{\]}\\}//'
Der erste Sed entfernt alle Zeilenumbrüche, der zweite entfernt den Text innerhalb der Tags.
Ich weiß nicht, ob Sie eine allgemeinere Lösung brauchen