Wie kann ich rekursiv über die Befehlszeile suchen und ersetzen?


84

Wie kann ich mit einer Shell wie bash oder zshell ein rekursives Suchen und Ersetzen durchführen? Mit anderen Worten, ich möchte jedes Vorkommen von 'foo' durch 'bar' in allen Dateien in diesem Verzeichnis und seinen Unterverzeichnissen ersetzen.


Eine alternative Antwort auf dieselben Fragen finden Sie hier stackoverflow.com/questions/9704020/…
dunxd


Es könnte eine gute Idee sein, dies in vim zu versuchen. Auf diese Weise können Sie die Bestätigungsfunktion verwenden, um sicherzustellen, dass Sie nichts austauschen, was Sie nicht beabsichtigen. Ich bin nicht sicher, ob es verzeichnisweit durchgeführt werden kann.
Samy Bencherif

Antworten:


106

Dieser Befehl wird es tun (getestet auf Mac OS X Lion und Kubuntu Linux).

# Recursively find and replace in files
find . -type f -name "*.txt" -print0 | xargs -0 sed -i '' -e 's/foo/bar/g'

So funktioniert das:

  1. find . -type f -name '*.txt'findet im aktuellen Verzeichnis ( .) und darunter alle regulären Dateien ( -type f), deren Namen auf enden.txt
  2. | Übergibt die Ausgabe dieses Befehls (eine Liste von Dateinamen) an den nächsten Befehl
  3. xargs sammelt diese Dateinamen und übergibt sie einzeln an sed
  4. sed -i '' -e 's/foo/bar/g'bedeutet "Bearbeiten Sie die Datei an Ort und Stelle, ohne eine Sicherung, und nehmen Sie die folgende Ersetzung ( s/foo/bar) mehrmals pro Zeile ( /g) vor" (siehe man sed)

Beachten Sie, dass der Teil 'ohne Backup' in Zeile 4 für mich in Ordnung ist, da die Dateien, die ich ändere, sowieso unter Versionskontrolle stehen, sodass ich bei einem Fehler leicht rückgängig machen kann.


9
Niemals eine Pipe-Find-Ausgabe an xargs ohne die -print0Option senden . Ihr Befehl schlägt bei Dateien mit Leerzeichen usw. im Namen fehl.
Slhck

20
Ich find -name '*.txt' -exec sed -i 's/foo/bar/g' {} +werde das alles auch mit GNU find machen.
Daniel Andersson

6
Ich bekomme, sed: can't read : No such file or directorywenn ich laufe find . -name '*.md' -print0 | xargs -0 sed -i '' -e 's/ä/ä/g', aber find . -name '*.md' -print0eine Liste mit vielen Dateien.
Martin Thoma

9
Dies funktioniert für mich, wenn ich das Leerzeichen zwischen dem -iund dem''
Kanadier Luke am

3
Was ist die Bedeutung von ''nach dem, sed -iwas ist die ''Rolle?
Jas

27
find . -type f -name "*.txt" -exec sed -i'' -e 's/foo/bar/g' {} +

Dadurch wird die xargsAbhängigkeit entfernt.


4
Dies funktioniert nicht mit GNU sed, wird also auf den meisten Systemen fehlschlagen. GNU sedverlangt, dass Sie kein Leerzeichen zwischen -iund setzen ''.
Slhck

1
Die akzeptierten Antworten erklären es besser. aber +1 für die Verwendung der richtigen Syntax.
Orodbhen

9

Wenn Sie Git verwenden, können Sie dies tun:

git grep -lz foo | xargs -0 sed -i '' -e 's/foo/bar/g'

-llistet nur Dateinamen auf. -zgibt nach jedem Ergebnis ein Null-Byte aus.

Am Ende tat ich dies, weil einige Dateien in einem Projekt keine neue Zeile am Ende der Datei hatten und sed eine neue Zeile hinzufügte, selbst wenn keine anderen Änderungen vorgenommen wurden. (Kein Kommentar, ob Dateien am Ende eine neue Zeile haben sollen oder nicht. 🙂)


1
Big +1 für diese Lösung. Der Rest der find ... -print0 | xargs -0 sed ...Lösungen dauert nicht nur viel länger, sondern fügt auch neue Zeilen zu Dateien hinzu, die noch keine haben. Dies ist ein Ärgernis, wenn Sie in einem Git-Repo arbeiten. git grepist im Vergleich schnell beleuchtet.
Josh Kupershmidt

3

Hier ist meine zsh / perl-Funktion, die ich dafür benutze:

change () {
        from=$1 
        shift
        to=$1 
        shift
        for file in $*
        do
                perl -i.bak -p -e "s{$from}{$to}g;" $file
                echo "Changing $from to $to in $file"
        done
}

Und ich würde es mit ausführen

$ change foo bar **/*.java

(zum Beispiel)


2

Versuchen:

sed -i 's/foo/bar/g' $(find . -type f)

Getestet unter Ubuntu 12.04.

Dieser Befehl funktioniert NICHT, wenn die Namen und / oder Dateinamen von Unterverzeichnissen Leerzeichen enthalten. Wenn Sie diese haben, verwenden Sie diesen Befehl nicht, da er nicht funktioniert.

Es ist im Allgemeinen eine schlechte Praxis, Leerzeichen in Verzeichnis- und Dateinamen zu verwenden.

http://linuxcommand.org/lc3_lts0020.php

Schauen Sie sich "Wichtige Fakten zu Dateinamen" an.


Probieren Sie es aus, wenn Sie Dateien mit Leerzeichen im Namen haben. (Es gibt eine Faustregel, die besagt: "Wenn etwas zu gut scheint, um wahr zu sein, ist es wahrscheinlich auch so." Wenn Sie eine Lösung "entdeckt" haben, die kompakter ist als alles, was in 3½ Jahren von anderen veröffentlicht wurde, sollten Sie sich fragen, warum das könnte sein.)
Scott

1

Verwenden Sie dieses Shell-Skript

Ich verwende jetzt dieses Shell-Skript, das Dinge kombiniert, die ich aus den anderen Antworten und aus der Suche im Web gelernt habe. Ich legte es in eine Datei namens changein einem Ordner auf meinem $PATHund tat es chmod +x change.

#!/bin/bash
function err_echo {
  >&2 echo "$1"
}

function usage {
  err_echo "usage:"
  err_echo '  change old new foo.txt'
  err_echo '  change old new foo.txt *.html'
  err_echo '  change old new **\*.txt'
  exit 1
}

[ $# -eq 0 ] && err_echo "No args given" && usage

old_val=$1
shift
new_val=$1
shift
files=$* # the rest of the arguments

[ -z "$old_val" ]  && err_echo "No old value given" && usage
[ -z "$new_val" ]  && err_echo "No new value given" && usage
[ -z "$files" ]    && err_echo "No filenames given" && usage

for file in $files; do
  sed -i '' -e "s/$old_val/$new_val/g" $file
done

0

Mein Anwendungsfall war ich ersetzen wollte foo:/Drive_Lettermit foo:/bar/baz/xyz konnte ich tun , um es mit dem folgenden Code in meinem Fall. Ich befand mich im selben Verzeichnis, in dem sich viele Dateien befanden.

find . -name "*.library" -print0 | xargs -0 sed -i '' -e 's/foo:\/Drive_Letter:/foo:\/bar\/baz\/xyz/g'

hoffe das hat geholfen.


0

Der folgende Befehl funktionierte unter Ubuntu und CentOS einwandfrei. Unter OS XI gab es jedoch immer wieder Fehler:

find . -name Root -exec sed -i 's/1.2.3.4\/home/foo.com\/mnt/' {} \;

sed: 1: "./Root": ungültiger Befehlscode.

Als ich versuchte, die Parameter über xargs zu übergeben, funktionierte das ohne Fehler:

find . -name Root -print0 | xargs -0 sed -i '' -e 's/1.2.3.4\/home/foo.com\/mnt/'

Die Tatsache , dass Sie sich verändert -izu -i ''ist wahrscheinlich wichtiger als die Tatsache , dass Sie geändert -execzu -print0 | xargs -0. Übrigens brauchen Sie das wahrscheinlich nicht -e.
Scott

0
# Recursively find and replace in files
find . -type f -name "*.txt" -print0 | xargs -0 sed -i '' -e 's/foo/bar/g'

Das Obige hat wie ein Zauber funktioniert, aber bei verknüpften Verzeichnissen muss ich ein -LFlag hinzufügen . Die endgültige Version sieht so aus:

# Recursively find and replace in files
find -L . -type f -name "*.txt" -print0 | xargs -0 sed -i '' -e 's/foo/bar/g'
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.