Ändern Sie mehrere Dateien

194

Der folgende Befehl ändert den Inhalt von 2 Dateien korrekt.

sed -i 's/abc/xyz/g' xaa1 xab1 

Was ich aber tun muss, ist, mehrere solcher Dateien dynamisch zu ändern, und ich kenne die Dateinamen nicht. Ich möchte einen Befehl schreiben, der alle Dateien aus dem aktuellen Verzeichnis liest, beginnend mit xa*und sedden Dateiinhalt ändern soll.

Shantanuo
quelle
61
Du meinst sed -i 's/abc/xyz/g' xa*?
Paul R
3
Die Antworten hier reichen nicht aus. Siehe unix.stackexchange.com/questions/112023/…
Isaac

Antworten:

135

Besser noch:

for i in xa*; do
    sed -i 's/asd/dfg/g' $i
done

weil niemand weiß, wie viele Dateien vorhanden sind und es einfach ist, Befehlszeilenlimits zu überschreiten.

Folgendes passiert, wenn zu viele Dateien vorhanden sind:

# grep -c aaa *
-bash: /bin/grep: Argument list too long
# for i in *; do grep -c aaa $i; done
0
... (output skipped)
#
lenik
quelle
18
Wenn so viele Dateien vorhanden sind, wird das Befehlszeilenlimit im Befehl überschritten for. Um sich find ... | xargs ...
davor
1
Ich kenne die Implementierung nicht, aber das "xa *" - Muster muss irgendwann erweitert werden. Führt die Shell die Erweiterung anders durch forals für echooder grep?
Glenn Jackman
4
siehe die aktualisierte Antwort. Wenn Sie weitere Informationen benötigen, stellen Sie bitte eine offizielle Frage, damit die Leute Ihnen helfen können.
Lenik
5
Im Befehl sed müssen Sie "$i"anstelle von verwenden $i, um die Wortteilung bei Dateinamen mit Leerzeichen zu vermeiden. Ansonsten ist das sehr schön.
Wildcard
4
In Bezug auf die Liste glaube ich, dass der Unterschied darin besteht, dass fores Teil der Sprachsyntax ist, nicht einmal nur eine eingebaute. Denn sed -i 's/old/new' *die Erweiterung von *muss ALL als Arglist an sed übergeben werden, und ich bin mir ziemlich sicher, dass dies geschehen muss, bevor der sedProzess überhaupt gestartet werden kann. Bei Verwendung der forSchleife wird die vollständige Arglist (die Erweiterung von *) niemals als Befehl übergeben, sondern nur im Shell-Speicher gespeichert und durchlaufen. Ich habe dafür überhaupt keine Referenz, es scheint nur wahrscheinlich, dass dies der Unterschied ist. (Ich würde gerne von jemandem hören, der besser informiert ist ...)
Wildcard
165

Ich bin überrascht, dass niemand das zu findende Argument -exec erwähnt hat, das für diese Art von Anwendungsfall vorgesehen ist, obwohl für jeden übereinstimmenden Dateinamen ein Prozess gestartet wird:

find . -type f -name 'xa*' -exec sed -i 's/asd/dsg/g' {} \;

Alternativ könnte man xargs verwenden, wodurch weniger Prozesse aufgerufen werden:

find . -type f -name 'xa*' | xargs sed -i 's/asd/dsg/g'

Oder verwenden Sie einfach die + exec-Variante anstelle von ;in find, damit find mehr als eine Datei pro Unterprozessaufruf bereitstellen kann:

find . -type f -name 'xa*' -exec sed -i 's/asd/dsg/g' {} +
ealfonso
quelle
7
Ich musste den Befehl in dieser Antwort wie folgt ändern: find ./ -type f -name 'xa*' -exec sed -i '' 's/asd/dsg/g' {} \;Das ist der Speicherort für den Befehl find ./und ein paar einfache Anführungszeichen danach -ifür OSX.
Shelbydz
Der Befehl find funktioniert so, wie er von ealfonso bereitgestellt wird. Er ./ist gleich .und -ihat nur den Parameter backupsuffix.
Uhausbrand
Die -execOption, zusammen mit zu finden, {} +reicht aus, um das angegebene Problem zu lösen, und sollte für die meisten Anforderungen in Ordnung sein. Ist xargsaber im Allgemeinen eine bessere Wahl, da es auch die parallele Verarbeitung mit der -pOption ermöglicht. Wenn Ihre Glob-Erweiterung groß genug ist, um Ihre Befehlszeilenlänge zu überschreiten, profitieren Sie wahrscheinlich auch von einer Beschleunigung über einen sequentiellen Lauf.
Amit Naidu
78

Sie könnten grep und sed zusammen verwenden. Auf diese Weise können Sie Unterverzeichnisse rekursiv durchsuchen.

Linux: grep -r -l <old> * | xargs sed -i 's/<old>/<new>/g'
OS X: grep -r -l <old> * | xargs sed -i '' 's/<old>/<new>/g'

For grep:
    -r recursively searches subdirectories 
    -l prints file names that contain matches
For sed:
    -i extension (Note: An argument needs to be provided on OS X)
Raj
quelle
3
Der Bonus dieser Methode für mich war, dass ich in einen grep -vgit-Ordner schlüpfen konntegrep -rl <old> . | grep -v \.git | xargs sed -i 's/<old>/<new>/g'
Martin Lyne
beste Lösung für einen Mac!
Marquis Blount
30

Diese Befehle funktionieren nicht in der Standardeinstellung sedvon Mac OS X.

Von man 1 sed:

-i extension
             Edit files in-place, saving backups with the specified
             extension.  If a zero-length extension is given, no backup 
             will be saved.  It is not recommended to give a zero-length
             extension when in-place editing files, as you risk corruption
             or partial content in situations where disk space is exhausted, etc.

Versucht

sed -i '.bak' 's/old/new/g' logfile*

und

for i in logfile*; do sed -i '.bak' 's/old/new/g' $i; done

Beide funktionieren gut.

Funroll
quelle
2
@sumek Hier ist eine Beispiel-Terminalsitzung unter OS X, die zeigt, wie sed alle Vorkommen ersetzt: GitHub Gist
Funroll
Ich habe dies verwendet, um zwei verschiedene Zeilen in allen Konfigurationsdateien meiner Website durch einen Einzeiler unten zu ersetzen. sed -i.bak "s / supercache_proxy_config / proxy_includes \ / supercache_config / g; s / basic_proxy_config / proxy_include \ / basic_proxy_config / g" sites-available / * Vergessen Sie nicht, die * .bak-Dateien zu löschen, wenn Sie mit der Datei fertig sind Systemhygiene willen.
Josiah
19

@PaulR hat dies als Kommentar gepostet, aber die Leute sollten es als Antwort ansehen (und diese Antwort funktioniert am besten für meine Bedürfnisse):

sed -i 's/abc/xyz/g' xa*

Dies funktioniert für eine moderate Anzahl von Dateien, wahrscheinlich in der Größenordnung von zehn, aber wahrscheinlich nicht in der Größenordnung von Millionen .

palswim
quelle
Angenommen, Sie haben Schrägstriche in Ihren Ersetzungen. Ein weiteres Beispiel mit Dateipfaden sed -i 's|auth-user-pass nordvpn.txt|auth-user-pass /etc/openvpn/nordvpn.txt|g' *.ovpn.
Léo Léopold Hertz 5
10

Eine andere vielseitigere Möglichkeit ist die Verwendung von find:

sed -i 's/asd/dsg/g' $(find . -type f -name 'xa*')
dkinzer
quelle
1
Die Ausgabe dieses Befehls find wird erweitert, sodass das Problem dadurch nicht behoben wird. Stattdessen sollten Sie -exec
ealfonso
@erjoalgo Dies funktioniert, da der Befehl sed mehrere Eingabedateien verarbeiten kann. Die Erweiterung des Befehls find ist genau das, was erforderlich ist, damit es funktioniert.
Dkinzer
Es funktioniert, solange die Anzahl der Dateien nicht an die Befehlszeilengrenzen stößt.
ealfonso
Dieses Limit hängt nur von den für den Computer verfügbaren Speicherressourcen ab und entspricht genau dem Limit für exec.
Dkinzer
4
Das stimmt einfach nicht. In Ihrem obigen Befehl wird $ (find. ...) zu einem einzigen Befehl erweitert, der sehr lang sein kann, wenn viele übereinstimmende Dateien vorhanden sind. Wenn es zu lang ist (in meinem System liegt das Limit beispielsweise bei 2097152 Zeichen), wird möglicherweise die Fehlermeldung "Argumentliste zu lang" angezeigt, und der Befehl schlägt fehl. Bitte googeln Sie diesen Fehler, um Hintergrundinformationen zu erhalten.
ealfonso
2

Ich benutze findfür ähnliche Aufgabe. Es ist ganz einfach: Sie müssen es als Argument für sedFolgendes übergeben:

sed -i 's/EXPRESSION/REPLACEMENT/g' `find -name "FILE.REGEX"`

Auf diese Weise müssen Sie keine komplexen Schleifen schreiben, und es ist einfach zu erkennen, welche Dateien Sie ändern möchten. Führen findSie sie einfach aus, bevor Sie sie ausführen sed.

Bluesboy
quelle
1
Dies ist genau das Gleiche wie die Antwort von @ dkinzer .
Herr Tao
0

du kannst machen

' xxxx ' Text wird gesucht und durch ' JJJJ ' ersetzt.

grep -Rn '**xxxx**' /path | awk -F: '{print $1}' | xargs sed -i 's/**xxxx**/**yyyy**/'
Mohamed Galal
quelle
0

Wenn Sie ein Skript ausführen können, habe ich Folgendes für eine ähnliche Situation getan:

Mithilfe eines Wörterbuchs / einer HashMap (assoziatives Array) und von Variablen für den sedBefehl können wir das Array durchlaufen, um mehrere Zeichenfolgen zu ersetzen. Wenn Sie einen Platzhalter in das Verzeichnis name_patterneinfügen, können Sie diese in Dateien durch ein Muster (dies könnte etwa so sein name_pattern='File*.txt') in einem bestimmten Verzeichnis ( source_dir) ersetzen . Alle Änderungen sind in der logfilein der geschriebendestin_dir

#!/bin/bash
source_dir=source_path
destin_dir=destin_path
logfile='sedOutput.txt'
name_pattern='File.txt'

echo "--Begin $(date)--" | tee -a $destin_dir/$logfile
echo "Source_DIR=$source_dir destin_DIR=$destin_dir "

declare -A pairs=( 
    ['WHAT1']='FOR1'
    ['OTHER_string_to replace']='string replaced'
)

for i in "${!pairs[@]}"; do
    j=${pairs[$i]}
    echo "[$i]=$j"
    replace_what=$i
    replace_for=$j
    echo " "
    echo "Replace: $replace_what for: $replace_for"
    find $source_dir -name $name_pattern | xargs sed -i "s/$replace_what/$replace_for/g" 
    find $source_dir -name $name_pattern | xargs -I{} grep -n "$replace_for" {} /dev/null | tee -a $destin_dir/$logfile
done

echo " "
echo "----End $(date)---" | tee -a $destin_dir/$logfile

Zuerst wird die Paare Array deklariert, ist jedes Paar eine Ersatzzeichenfolge, dann WHAT1wird für die ersetzt werden FOR1und OTHER_string_to replacewird ersetzt string replacedin der Datei File.txt. In der Schleife wird das Array gelesen, das erste Mitglied des Paares wird als replace_what=$iund das zweite als abgerufen replace_for=$j. Der findBefehl durchsucht im Verzeichnis den Dateinamen (der möglicherweise einen Platzhalter enthält) und der sed -iBefehl ersetzt in derselben Datei (en) die zuvor definierten. Schließlich habe ich grepder Protokolldatei eine umgeleitete Datei hinzugefügt , um die in den Dateien vorgenommenen Änderungen zu protokollieren.

Dies funktionierte für mich in GNU Bash 4.3 sed 4.2.2und basierte auf VasyaNovikovs Antwort für Loop over Tuples in Bash .

Lejuanjowski
quelle