Das Ersetzen von Zeichenfolgen in Dateien basierend auf bestimmten Suchkriterien ist eine sehr häufige Aufgabe. Wie kann ich
- Ersetzen Sie die Zeichenfolge
foo
mitbar
in allen Dateien im aktuellen Verzeichnis? - dasselbe rekursiv für Unterverzeichnisse tun?
- nur ersetzen, wenn der Dateiname mit einer anderen Zeichenfolge übereinstimmt?
- nur ersetzen, wenn der String in einem bestimmten Kontext gefunden wird?
- Ersetzen, wenn der String auf einer bestimmten Zeilennummer steht?
- mehrere Zeichenfolgen durch denselben Ersatz ersetzen
- mehrere Zeichenfolgen durch unterschiedliche Ersetzungen ersetzen
Akzeptierte Antwort:
1. Ersetzen aller Vorkommen einer Zeichenfolge durch eine andere in allen Dateien im aktuellen Verzeichnis:
Diese sind für Fälle, in denen Sie wissen dass das Verzeichnis nur reguläre Dateien enthält und Sie alle nicht versteckten Dateien verarbeiten möchten. Wenn dies nicht der Fall ist, verwenden Sie die Ansätze in 2.
Alle sed
Lösungen in dieser Antwort setzen GNU sed
voraus . Wenn Sie FreeBSD oder macOS verwenden, ersetzen Sie -i
mit -i ''
. Beachten Sie auch, dass die Verwendung des -i
wechseln Sie mit einer beliebigen Version von sed
hat bestimmte Auswirkungen auf die Dateisystemsicherheit und ist in keinem Skript ratsam, das Sie in irgendeiner Weise verteilen möchten.
-
Nicht rekursiv, Dateien nur in diesem Verzeichnis:
sed -i -- 's/foo/bar/g' * perl -i -pe 's/foo/bar/g' ./*
(die perl
einer schlägt fehl für Dateinamen, die mit |
enden oder Leerzeichen)).
-
Rekursive, reguläre Dateien (einschließlich versteckter ) in diesem und allen Unterverzeichnissen
find . -type f -exec sed -i 's/foo/bar/g' {} +
Wenn Sie zsh verwenden:
sed -i -- 's/foo/bar/g' **/*(D.)
(kann fehlschlagen, wenn die Liste zu groß ist, siehe
zargs
zu umgehen).Bash kann nicht direkt nach regulären Dateien suchen, eine Schleife ist erforderlich (geschweifte Klammern vermeiden das globale Setzen der Optionen):
( shopt -s globstar dotglob; for file in **; do if [[ -f $file ]] && [[ -w $file ]]; then sed -i -- 's/foo/bar/g' "$file" fi done )
Die Dateien werden ausgewählt, wenn sie tatsächliche Dateien sind (-f) und beschreibbar sind (-w).
2. Nur ersetzen, wenn der Dateiname mit einer anderen Zeichenfolge übereinstimmt / eine bestimmte Erweiterung hat / von einem bestimmten Typ ist usw.:
-
Nicht rekursiv, Dateien nur in diesem Verzeichnis:
sed -i -- 's/foo/bar/g' *baz* ## all files whose name contains baz sed -i -- 's/foo/bar/g' *.baz ## files ending in .baz
-
Rekursive, reguläre Dateien in diesem und allen Unterverzeichnissen
find . -type f -name "*baz*" -exec sed -i 's/foo/bar/g' {} +
Wenn Sie Bash verwenden (geschweifte Klammern vermeiden das globale Setzen der Optionen):
( shopt -s globstar dotglob sed -i -- 's/foo/bar/g' **baz* sed -i -- 's/foo/bar/g' **.baz )
Wenn Sie zsh verwenden:
sed -i -- 's/foo/bar/g' **/*baz*(D.) sed -i -- 's/foo/bar/g' **/*.baz(D.)
Der --
dient dazu, sed
mitzuteilen dass keine Flags mehr in der Kommandozeile angegeben werden. Dies ist nützlich, um Dateinamen zu schützen, die mit -
beginnen .
-
Ist eine Datei von einem bestimmten Typ, beispielsweise ausführbar (siehe
man find
für weitere Optionen):find . -type f -executable -exec sed -i 's/foo/bar/g' {} +
zsh
:
sed -i -- 's/foo/bar/g' **/*(D*)
3. Nur ersetzen, wenn der String in einem bestimmten Kontext gefunden wird
-
Ersetzen Sie
foo
mitbar
nur wenn einbaz
vorhanden ist später in derselben Zeile:sed -i 's/foo(.*baz)/bar1/' file
In sed
, mit ( )
speichert, was in Klammern steht, und Sie können dann mit 1
darauf zugreifen . Es gibt viele Variationen dieses Themas, um mehr über solche regulären Ausdrücke zu erfahren, siehe hier.
-
Ersetzen Sie
foo
mitbar
nur wennfoo
befindet sich in der 3D-Spalte (Feld) der Eingabedatei (unter der Annahme von durch Leerzeichen getrennten Feldern):gawk -i inplace '{gsub(/foo/,"baz",$3); print}' file
(benötigt gawk
4.1.0 oder neuer).
-
Verwenden Sie für ein anderes Feld einfach
$N
wobeiN
ist die Nummer des Interessengebiets. Für ein anderes Feldtrennzeichen (:
in diesem Beispiel) verwenden Sie:gawk -i inplace -F':' '{gsub(/foo/,"baz",$3);print}' file
Eine andere Lösung mit perl
:
perl -i -ane '$F[2]=~s/foo/baz/g; $" = " "; print "@Fn"' foo
HINWEIS:Sowohl das awk
und perl
Lösungen wirken sich auf die Abstände in der Datei aus (entfernen Sie die führenden und abschließenden Leerzeichen und wandeln Sie in den übereinstimmenden Zeilen Sequenzen von Leerzeichen in ein Leerzeichen um). Verwenden Sie für ein anderes Feld $F[N-1]
wobei N
die gewünschte Feldnummer ist und für ein anderes Feldtrennzeichen verwendet wird (der $"=":"
setzt das Ausgabefeldtrennzeichen auf :
):
perl -i -F':' -ane '$F[2]=~s/foo/baz/g; $"=":";print "@F"' foo
-
Ersetzen Sie
foo
mitbar
nur in der 4. Zeile:sed -i '4s/foo/bar/g' file gawk -i inplace 'NR==4{gsub(/foo/,"baz")};1' file perl -i -pe 's/foo/bar/g if $.==4' file
4. Mehrere Ersetzungsoperationen:Ersetzen durch verschiedene Zeichenfolgen
-
Sie können
sed
kombinieren Befehle:sed -i 's/foo/bar/g; s/baz/zab/g; s/Alice/Joan/g' file
Beachten Sie, dass die Reihenfolge wichtig ist (sed 's/foo/bar/g; s/bar/baz/g'
ersetzt foo
mit baz
).
-
oder Perl-Befehle
perl -i -pe 's/foo/bar/g; s/baz/zab/g; s/Alice/Joan/g' file
-
Wenn Sie eine große Anzahl von Mustern haben, ist es einfacher, Ihre Muster und ihre Ersetzungen in einem
sed
zu speichern Skriptdatei:#! /usr/bin/sed -f s/foo/bar/g s/baz/zab/g
-
Oder, wenn Sie zu viele Musterpaare haben, als dass das obige durchführbar wäre, können Sie Musterpaare aus einer Datei lesen (zwei durch Leerzeichen getrennte Muster, $Muster und $Ersetzung, pro Zeile):
while read -r pattern replacement; do sed -i "s/$pattern/$replacement/" file done < patterns.txt
-
Das wird bei langen Listen von Mustern und großen Datendateien ziemlich langsam sein, also möchten Sie vielleicht die Muster lesen und einen
sed
erstellen Skript von ihnen stattdessen. Im Folgenden wird von einem <Leerzeichen> ausgegangen Trennzeichen trennt eine Liste von MATCH<Leerzeichen>REPLACE Paare, die in der Dateipatterns.txt
einzeln pro Zeile vorkommen :sed 's| *([^ ]*) *([^ ]*).*|s/1/2/g|' <patterns.txt | sed -f- ./editfile >outfile
Das obige Format ist weitgehend willkürlich und lässt beispielsweise kein <Leerzeichen> zu in einem von MATCH oder ERSETZEN . Die Methode ist jedoch sehr allgemein:im Grunde, wenn Sie einen Ausgabestrom erstellen können, der wie ein sed
aussieht script, dann können Sie diesen Stream als sed
beziehen Skript durch Angabe von sed
’s-Skriptdatei als -
stdin.
-
Sie können mehrere Skripte auf ähnliche Weise kombinieren und verketten:
SOME_PIPELINE | sed -e'#some expression script' -f./script_file -f- -e'#more inline expressions' ./actual_edit_file >./outfile
Ein POSIX sed
verkettet alle Skripte in der Reihenfolge, in der sie auf der Befehlszeile erscheinen, zu einem. Keines davon muss auf ein n
enden ewline.
-
grep
kann genauso funktionieren:sed -e'#generate a pattern list' <in | grep -f- ./grepped_file
-
Wenn Sie mit festen Zeichenfolgen als Muster arbeiten, empfiehlt es sich, Metazeichen regulärer Ausdrücke mit Escapezeichen zu versehen . Das geht ganz einfach:
sed 's/[]$&^*./[]/\&/g s| *([^ ]*) *([^ ]*).*|s/1/2/g| ' <patterns.txt | sed -f- ./editfile >outfile
5. Mehrere Ersetzungsoperationen:Ersetzen Sie mehrere Muster durch dieselbe Zeichenfolge
-
Ersetzen Sie
foo
,bar
oderbaz
mitfoobar
sed -Ei 's/foo|bar|baz/foobar/g' file
-
oder
perl -i -pe 's/foo|bar|baz/foobar/g' file