Wie grep und ersetzen

251

Ich muss rekursiv nach einer angegebenen Zeichenfolge in allen Dateien und Unterverzeichnissen in einem Verzeichnis suchen und diese Zeichenfolge durch eine andere Zeichenfolge ersetzen.

Ich weiß, dass der Befehl zum Finden folgendermaßen aussehen könnte:

grep 'string_to_find' -r ./*

Aber wie kann ich jede Instanz von string_to_finddurch eine andere Zeichenfolge ersetzen ?

Billtian
quelle
Ich glaube nicht, dass grep das kann (ich könnte mich irren). Einfachere Möglichkeiten wären, Sed oder Perl zu verwenden, um das Ersetzen
Memento Mori
2
Versuchen Sie zu verwendensed -i 's/.*substring.*/replace/'
Eddy_Em
2
@Eddy_Em Das ersetzt die gesamte Zeile durch Ersetzen. Sie müssen die Gruppierung verwenden, um den Teil der Zeile vor und nach dem Teilstring zu erfassen und diesen dann in die Ersatzzeile einzufügen. sed -i 's/\(.*\)substring\(.*\)/\1replace\2/'
JStrahl

Antworten:

248

Eine andere Möglichkeit besteht darin, find zu verwenden und es dann durch sed zu leiten.

find /path/to/files -type f -exec sed -i 's/oldstring/new string/g' {} \;
rezizter
quelle
34
Unter OS X 10.10 Terminal ist eine ordnungsgemäße Erweiterungszeichenfolge für den Parameter -ierforderlich. Wenn Sie beispielsweise find /path/to/files -type f -exec sed -i "" "s/oldstring/new string/g" {} \;eine leere Zeichenfolge
angeben, wird
10
Warum bekomme ich "sed: RE error: unzulässige Bytesequenz". Und ja, ich habe das -i ""für OS X hinzugefügt. Es funktioniert anders.
Taco
2
Ich hatte das Problem mit der illegalen Byte-Sequenz unter macOS 10.12 und diese Frage / Antwort löste mein Problem: stackoverflow.com/questions/19242275/… .
Abeboparebop
3
Dies berührt jede Datei, sodass die Dateizeiten geändert werden. und wandelt Zeilenenden von CRLFbis LFunter Windows.
JWW
183

Ich habe die Antwort bekommen.

grep -rl matchstring somedir/ | xargs sed -i 's/string1/string2/g'
Billtian
quelle
14
Dies würde die passenden Dateien zweimal durchsuchen ... einmal mit grepund dann noch einmal mit sed. Die Verwendung der findMethode ist effizienter, aber diese von Ihnen erwähnte Methode funktioniert.
Cmevoli
41
Unter OS X müssen Sie zu ändern sed -i 's/str1/str2/g', sed -i "" 's/str1/str2/g'damit dies funktioniert.
JDF
6
@cmevoli geht mit dieser Methode grepalle Dateien durch und sedscannt nur die Dateien, die mit übereinstimmen grep. Mit der findMethode in der anderen Antwort werden findzuerst alle Dateien aufgelistet und dann sedalle Dateien in diesem Verzeichnis durchsucht. So dass diese Methode nicht unbedingt langsamer ist, hängt davon ab , wie viele Spiele gibt es , und die Unterschiede in den Suchgeschwindigkeiten zwischen sed, grepund find.
Joelostblom
4
OTOH auf diese Weise können Sie VORSCHAUEN, was grep vor dem tatsächlichen Ersetzen findet, wodurch das Risiko eines Ausfalls erheblich verringert wird, insbesondere für Regex n00bs wie mich
Lennart Rolland
2
Dies ist auch nützlich, wenn Ihr Grep-Ersatz klüger als sed ist. Zum Beispiel gehorcht ripgrep .gitignore, während sed dies nicht tut.
user31389
43

Sie könnten es sogar so machen:

Beispiel

grep -rl 'windows' ./ | xargs sed -i 's/windows/linux/g'

Dadurch wird in allen Dateien relativ zum aktuellen Verzeichnis nach der Zeichenfolge ' windows ' gesucht und ' windows ' bei jedem Auftreten der Zeichenfolge in jeder Datei durch ' linux ' ersetzt.

Dulith De Costa
quelle
2
Dies grepist nur nützlich, wenn Dateien vorhanden sind, die nicht geändert werden sollten. Laufen sedauf allen Dateien aktualisieren das Änderungsdatum der Datei , aber lassen Sie die Inhalte unverändert , wenn es keine Übereinstimmungen gibt.
Tripleee
@tripleee: Sei vorsichtig mit ... aber [sed] lasse den Inhalt unverändert, wenn es keine Übereinstimmungen gibt. " Bei der Verwendung ändert sich -imeiner Meinung sednach die Dateizeit jeder Datei, die berührt wird, obwohl der Inhalt unverändert bleibt. sedKonvertiert auch Zeilenenden Ich verwende es nicht sedunter Windows in einem Git-Repo, da alle CRLFin geändert wurden LF.
jww
Dieser Befehl benötigt ein "" nach dem -i, um anzuzeigen, dass nach der direkten Ersetzung zumindest in macosx keine Sicherungsdateien erstellt werden. Überprüfen Sie die Manpage für Details. Wenn Sie eine Sicherung wünschen, legen Sie hier die Erweiterung der zu erstellenden Datei ab.
spinyBabbler
31

Dies funktioniert am besten für mich unter OS X:

grep -r -l 'searchtext' . | sort | uniq | xargs perl -e "s/matchtext/replacetext/" -pi

Quelle: http://www.praj.com.au/post/23691181208/grep-replace-text-string-in-files

Marc Juchli
quelle
Dies ist perfekt! funktioniert auch mit ag:ag "search" -l -r . | sort | uniq | xargs perl -e 's/search/replace' -pi
@sebastiankeller Ihrem Perl-Befehl fehlt der letzte Schrägstrich, was ein Syntaxfehler ist.
Tripleee
3
Warum ist der sort -ugerade Teil davon? Unter welchen Umständen würden Sie erwarten grep -rl, zweimal denselben Dateinamen zu erstellen?
Tripleee
5

Andere Lösungen mischen Regex-Syntaxen. Um Perl / PCRE-Muster sowohl zum Suchen als auch zum Ersetzen zu verwenden und nur übereinstimmende Dateien zu verarbeiten, funktioniert dies recht gut:

grep -rlZPi 'match1' | xargs -0r perl -pi -e 's/match2/replace/gi;'

wo match1und match2sind normalerweise identisch, match1können aber vereinfacht werden, um erweiterte Funktionen zu entfernen, die nur für die Ersetzung relevant sind, z. B. das Erfassen von Gruppen.

Übersetzung: greprekursiv und Listen von Dateien, die diesem PCRE-Muster entsprechen, durch nul getrennt, um Sonderzeichen im Dateinamen zu schützen, und dann die Dateinamen weiterzuleiten, xargsdie eine durch Null getrennte Liste erwarten, aber nichts tun, wenn keine Namen empfangen werden. und perlzu Ersatzzeilen gelangen , in denen Übereinstimmungen gefunden werden.

Fügen Sie den ISchalter hinzu grep, um Binärdateien zu ignorieren. Löschen Sie für Übereinstimmungen zwischen Groß- und Kleinschreibung den iSchalter von grepund das iFlag, das an den Substitutionsausdruck angehängt ist, aber nicht den iSchalter an perlsich.

Walf
quelle
Perl selbst ist durchaus in der Lage, eine Dateistruktur zu rekursieren. In der Tat gibt es ein Tool, find2perldas im Lieferumfang von Perl enthalten ist und solche Dinge ohne xargsTricks erledigt .
Tripleee
@tripleee durchsucht findkeine Dateiinhalte und es geht darum, nur übereinstimmende Dateien zu verarbeiten, ohne ein Perl-Programm zu schreiben.
Walf
Dies ist eine gute Lösung für Windows, da das Problem der Konvertierung der Zeilenenden durch sed-basierte Lösungen vermieden wird. Vielen Dank!
JamHandy
4

Normalerweise nicht mit grep, sondern mit sed -i 's/string_to_find/another_string/g'oder perl -i.bak -pe 's/string_to_find/another_string/g'.

Minopret
quelle
3

Seien Sie sehr vorsichtig bei der Verwendung findund sedin einem Git Repo! Wenn Sie die Binärdateien nicht ausschließen, kann dieser Fehler auftreten:

error: bad index file sha1 signature 
fatal: index file corrupt

Um diesen Fehler zu beheben, müssen Sie den Fehler beheben, sedindem Sie Ihren new_stringdurch Ihren ersetzen old_string. Dadurch werden Ihre ersetzten Zeichenfolgen zurückgesetzt, sodass Sie wieder am Anfang des Problems stehen.

Der richtige Weg, um nach einem String zu suchen und ihn zu ersetzen, besteht darin, ihn zu überspringen findund grepstattdessen zu verwenden, um die Binärdateien zu ignorieren:

sed -ri -e "s/old_string/new_string/g" $(grep -Elr --binary-files=without-match "old_string" "/files_dir")

Credits für @hobs

tsveti_iko
quelle
1

Folgendes würde ich tun:

find /path/to/dir -type f -iname "*filename*" -print0 | xargs -0 sed -i '/searchstring/s/old/new/g'

Dies sucht nach allen Dateien, die filenameim Dateinamen unter der /path/to/dirOption enthalten sind, und sucht nach der Zeile mit searchstringund ersetzt olddurch new.

Wenn Sie jedoch die Suche nach einer bestimmten Datei mit einer filenameZeichenfolge im Dateinamen unterlassen möchten, tun Sie einfach Folgendes:

find /path/to/dir -type f -print0 | xargs -0 sed -i '/searchstring/s/old/new/g'

Dies wird das Gleiche wie oben tun, jedoch für alle Dateien, die unter gefunden werden /path/to/dir.

Tinnick
quelle
0

Eine andere Möglichkeit wäre, nur Perl mit Globstar zu verwenden.

Durch Aktivieren shopt -s globstarin Ihrem .bashrc(oder wo auch immer) kann das **Glob-Muster rekursiv mit allen Unterverzeichnissen und Dateien übereinstimmen.

So verwenden perl -pXe 's/SEARCH/REPLACE/g' -i **rekursiv ersetzen SEARCHmit REPLACE.

Das -XFlag weist Perl an, "alle Warnungen zu deaktivieren" - was bedeutet, dass es sich nicht über Verzeichnisse beschwert.

Der globstar ermöglicht es Ihnen auch , wie die Dinge zu tun , sed -i 's/SEARCH/REPLACE/g' **/*.extwenn man wollte ersetzen SEARCHmit REPLACEin allen untergeordneten Dateien mit der Erweiterung .ext.

Schuldiger Delphin
quelle
"Eine andere Möglichkeit wäre, nur Perl mit Globstar zu verwenden ..." - Nicht auf Posixy-Computern wie Solaris. Deshalb suche ich speziell grepund sed.
JWW