Globales Suchen und Ersetzen der Linux-Befehlszeile

80

Ich versuche, eine Zeichenfolge in allen Dateien zu suchen und zu ersetzen, die mit grep auf einem Linux-Computer übereinstimmen. Ich habe einige Teile von dem, was ich tun möchte, bin mir aber nicht sicher, wie ich sie am besten aneinander reihen soll.

grep -n 'foo' * wird mir Ausgabe in der Form geben:

[filename]:[line number]:[text]

Für jede von grep zurückgegebene Datei möchte ich "foo" durch "bar" ersetzen und das Ergebnis in die Datei zurückschreiben. Gibt es eine gute Möglichkeit, das zu tun? Vielleicht eine schicke Pipeline?

Michael Kristofik
quelle
Können Sie Ihre Frage umformulieren? Es ist mir nicht klar ...
Eineki

Antworten:

70

Meinen Sie das Suchen und Ersetzen einer Zeichenfolge in allen Dateien, die mit grep übereinstimmen?

perl -p -i -e 's/oldstring/newstring/g' `grep -ril searchpattern *`

Bearbeiten

Da dies eine ziemlich beliebte Frage zu sein scheint, dachte ich, ich würde sie aktualisieren.

Heutzutage benutze ich meistens, ack-grepweil es benutzerfreundlicher ist. Der obige Befehl wäre also:

perl -p -i -e 's/old/new/g' `ack -l searchpattern`

So behandeln Sie Leerzeichen in Dateinamen:

ack --print0 -l searchpattern | xargs -0 perl -p -i -e 's/old/new/g'

Sie können mehr damit machen ack-grep. Angenommen, Sie möchten die Suche nur auf HTML-Dateien beschränken:

ack --print0 --html -l searchpattern | xargs -0 perl -p -i -e 's/old/new/g'

Und wenn Leerraum kein Thema ist, ist er noch kürzer:

perl -p -i -e 's/old/new/g' `ack -l --html searchpattern`
perl -p -i -e 's/old/new/g' `ack -f --html` # will match all html files
Armandino
quelle
3
Ich denke, dies könnte ein Problem mit Dateien / Ordnern haben, die Leerzeichen enthalten. Can't open Untitled: No such file or directory, <> line 5beim Versuch "Untitled Folder / file.txt".
Xeoncross
108

Dies scheint das zu sein, was Sie wollen, basierend auf dem Beispiel, das Sie gegeben haben:

sed -i 's/foo/bar/g' *

Es ist nicht rekursiv (es wird nicht in Unterverzeichnisse absteigen). Für eine nette Lösung, die in ausgewählten Dateien in einem Baum ersetzt wird, würde ich find verwenden:

find . -name '*.html' -print -exec sed -i.bak 's/foo/bar/g' {} \;

Das *.htmlist der Ausdruck , dass Dateien müssen übereinstimmen, die .baknach dem -ieine Kopie der Originaldatei macht, mit der Erweiterung .bak (es kann jede Erweiterung , die Sie wie sein) und der gam Ende des sed Ausdruck Tells auf mehrere Kopien sed ersetzen eine Zeile (und nicht nur die erste). Das -printzu finden ist eine Bequemlichkeit, um zu zeigen, welche Dateien abgeglichen wurden. All dies hängt von den genauen Versionen dieser Tools auf Ihrem System ab.

MattJ
quelle
1
Ein Wort der Warnung für Cygwin-Benutzer. Die Kombination find and sed scheint die Benutzerrechte für die Dateien zu ändern, durch die gestreamt wird. Dies kann einfach behoben werden, indem der Befehl chmod -R 644 * aus derselben Verzeichnisstufe verwendet wird, die beim Ausführen von find / sed verwendet wurde.
Kaskelotti
Ein Wort der Warnung an die Leute, die das Argument -i nicht verwenden wollen: Wenn Sie es nicht verwenden, funktioniert es nicht (fragen Sie mich nicht warum)
knocte
4
@knocte -i weist sed an, die Datei zu ändern, andernfalls wird nur die geänderte Version in stdout gedruckt. Wenn Sie nicht möchten, dass die .bak-Datei erstellt wird, lassen Sie einfach den '.bak'-Teil weg. -I funktioniert auch eigenständig.
MattJ
2
Unter OSX müssen Sie dem findBefehl ein Verzeichnis geben, von dem aus Sie beispielsweise find . -name '*.html'oder starten können find directoryname/ -name '*'.
Michiel Kauw-A-Tjoe
Ich musste ein -e hinzufügen, sonst dachte es, der 's ...' Teil sei das Suffixsed -ie 's/foo/bar/g' *
vish
14

Wenn Sie sed(1)eine -iOption haben, verwenden Sie diese wie folgt:

for i in *; do
  sed -i 's/foo/bar/' $i
done

Wenn nicht, gibt es verschiedene Möglichkeiten, je nachdem, mit welcher Sprache Sie spielen möchten:

ruby -i.bak -pe 'sub(%r{foo}, 'bar')' *
perl -pi.bak -e 's/foo/bar/' *
Keltia
quelle
2
for i in *; do ...Ist redundant, kann sed eine Liste von Dateien als Argumente verwenden.
Jens
2
@Jens warum nicht diese Antwort verbessern, indem Sie Ihr eigenes Beispiel zu dem obigen hinzufügen?
Elster
Variablen sollten immer zitiert werdenfor i in *; do; sed -i 's/foo/bar/g' "$i"; done
Katze
6

Ich mag und verwende die obige Lösung oder eine systemweite Suche und Ersetzung unter Tausenden von Dateien:

find -name '*.htm?' -print -exec sed -i.bak 's/foo/bar/g' {} \;

Ich nehme an mit dem '* .htm?' Anstelle von .html werden .htm- und .html-Dateien gleichermaßen gesucht und gefunden.

Ich ersetze die .bak durch die systemweit verwendete Tilde (~), um das Bereinigen von Sicherungsdateien zu vereinfachen.

Hans
quelle
4

Dies funktioniert mit grep, ohne dass Perl oder Find verwendet werden muss.

grep -rli 'old-word' * | xargs -i@ sed -i 's/old-word/new-word/g' @
Pymarco
quelle
xargs hat unter OSX oder BSD kein -i openbsd.org/cgi-bin/man.cgi/OpenBSD-current/man1/… wollten Sie ein Großbuchstaben "I" verwenden?
Tony Adams
Ich wusste nicht, dass -ies für andere Betriebssysteme nicht funktioniert. Funktioniert für mich auf Ubuntu.
Pymarco
Meine xargsManpage (unter Ubuntu) sagt, dass dies -iveraltet ist und -Istattdessen verwendet werden soll. Also sollten wir sagen:grep -rli 'old-word' * | xargs -I filepath sed -i 's/old-word/new-word/g' filepath
Max Wallace
3

find . -type f -print0 | xargs -0 <sed/perl/ruby cmd>verarbeitet mehrere in Speicherplätzen enthaltene Dateinamen gleichzeitig und lädt einen Interpreter pro Stapel. Viel schneller.

koolb
quelle
@knocte, "cmd" ist ein Token für den gesamten Such- und Ersetzungsbefehl für das jeweilige Tool. Diese Antwort beantwortet die Frage, wie mit den in Leerzeichen enthaltenen Dateinamen umgegangen werden soll.
Tony Adams
1

Die bereits gegebene Antwort auf die Verwendung von find und sed

find -name '*.html' -print -exec sed -i.bak 's/foo/bar/g' {} \;

ist wahrscheinlich die Standardantwort. Oder Sie könnten perl -pi -e s/foo/bar/g'anstelle des sedBefehls verwenden.

Für die meisten schnellen Anwendungen ist der Befehl rpl möglicherweise leichter zu merken. Hier ist eine rekursive Ersetzung (foo -> bar) für alle Dateien im aktuellen Verzeichnis:

rpl -R foo bar .

Es ist in den meisten Linux-Distributionen nicht standardmäßig verfügbar, lässt sich jedoch schnell installieren ( apt-get install rploder ähnliches).

Für härtere Jobs, die reguläre Ausdrücke und Rückersetzung oder das Umbenennen von Dateien sowie das Suchen und Ersetzen beinhalten, ist das allgemeinste und leistungsfähigste Tool, das mir bekannt ist, repren , ein kleines Python-Skript, das ich vor einiger Zeit für einige geschrieben habe dornigere Umbenennungs- und Refactoring-Aufgaben. Die Gründe, die Sie vielleicht bevorzugen, sind:

  • Unterstützt das Umbenennen von Dateien sowie das Suchen und Ersetzen von Dateiinhalten (einschließlich des Verschiebens von Dateien zwischen Verzeichnissen und des Erstellens neuer übergeordneter Verzeichnisse).
  • Sehen Sie sich die Änderungen an, bevor Sie die Suche und das Ersetzen durchführen.
  • Unterstützen Sie reguläre Ausdrücke mit den Modi "Rücksubstitution", "Ganzes Wort", "Groß- und Kleinschreibung nicht berücksichtigen" und "Groß- / Kleinschreibung beibehalten" (foo -> Balken, Foo -> Balken, FOO -> BAR ersetzen).
  • Funktioniert mit mehreren Ersetzungen, einschließlich Swaps (foo -> bar und bar -> foo) oder Sätzen nicht eindeutiger Ersetzungen (foo -> bar, f -> x).

Überprüfen Sie die README für Beispiele.

jlevy
quelle
1

Das ist eigentlich einfacher als es scheint.

grep -Rl 'foo' ./ | xargs -n 1 -I % sh -c "ls %; sed -i 's/foo/bar/g' %";
  • grep rekursiert durch Ihren Baum (-R) und druckt nur den Dateinamen (-l), beginnend mit dem aktuellen Verzeichnis (./).
  • Das wird an xargs weitergeleitet, das sie einzeln verarbeitet (-n 1) und% als Platzhalter (-I%) in einem Shell-Befehl (sh -c) verwendet.
  • Im Shell-Befehl wird zuerst der Dateiname gedruckt (ls%;)
  • dann führt sed eine Inline-Operation (-i) durch, eine Ersetzung ('s /') von foo mit bar (foo / bar), global (/ g) in der Datei (wiederum dargestellt durch%).

Kinderleicht. Wenn Sie einen guten Überblick über find, grep, xargs, sed und awk haben, ist fast nichts unmöglich, wenn es um die Manipulation von Textdateien in bash geht :)

Siliconrockstar
quelle
Bah, ignoriere, dieser Pymarco hat dies bereits oben behandelt. Ich lasse meine Antwort für die Erklärung.
Siliconrockstar