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.
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.
Antworten:
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:
find . -type f -name '*.txt'
findet im aktuellen Verzeichnis ( .
) und darunter alle regulären Dateien ( -type f
), deren Namen auf enden.txt
|
Übergibt die Ausgabe dieses Befehls (eine Liste von Dateinamen) an den nächsten Befehlxargs
sammelt diese Dateinamen und übergibt sie einzeln an sed
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.
-print0
Option senden . Ihr Befehl schlägt bei Dateien mit Leerzeichen usw. im Namen fehl.
find -name '*.txt' -exec sed -i 's/foo/bar/g' {} +
werde das alles auch mit GNU find machen.
sed: can't read : No such file or directory
wenn ich laufe find . -name '*.md' -print0 | xargs -0 sed -i '' -e 's/ä/ä/g'
, aber find . -name '*.md' -print0
eine Liste mit vielen Dateien.
-i
und dem''
''
nach dem, sed -i
was ist die ''
Rolle?
find . -type f -name "*.txt" -exec sed -i'' -e 's/foo/bar/g' {} +
Dadurch wird die xargs
Abhängigkeit entfernt.
sed
, wird also auf den meisten Systemen fehlschlagen. GNU sed
verlangt, dass Sie kein Leerzeichen zwischen -i
und setzen ''
.
Wenn Sie Git verwenden, können Sie dies tun:
git grep -lz foo | xargs -0 sed -i '' -e 's/foo/bar/g'
-l
listet nur Dateinamen auf. -z
gibt 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. 🙂)
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 grep
ist im Vergleich schnell beleuchtet.
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)
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.
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 change
in einem Ordner auf meinem $PATH
und 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
Mein Anwendungsfall war ich ersetzen wollte
foo:/Drive_Letter
mit 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.
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/'
-i
zu -i ''
ist wahrscheinlich wichtiger als die Tatsache , dass Sie geändert -exec
zu -print0 | xargs -0
. Übrigens brauchen Sie das wahrscheinlich nicht -e
.
# 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 -L
Flag 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'