Wie führe ich eine sed-in-place-Ersetzung durch, bei der nur Backups von Dateien erstellt werden, die geändert wurden?

13

Ich habe Folgendes ausgeführt, um einen Begriff zu ersetzen, der in allen Dateien im aktuellen Arbeitsverzeichnis verwendet wird:

$ find . -type f -print0 | xargs -0 sed -i'.bup' -e's/Ms. Johnson/Mrs. Melbin/g'

Dies führte die Wortersetzung durch, erzeugte aber auch .bupDateien von Dateien, die nie die Ms. JohnsonZeichenfolge hatten.

Wie führe ich die Ersetzung durch, ohne all diese unnötigen Sicherungen zu erstellen?

Belmin Fernandez
quelle
kommt mir vor wie ein bug von sed see
hotschke
Es ist wahrscheinlich ein besserer Ansatz möglich ex, der :!cp '%' '%.bup'vor dem Speichern und Beenden ausgeführt wird (nur wenn die Datei geändert wurde) . Könnte einen Blick wert sein.
Wildcard
Ich habe oben eine Frage zu meiner Idee über den viStapelaustausch geschrieben.
Wildcard

Antworten:

6

Sie können den Inhalt der Dateien überprüfen, um sicherzustellen, dass eine Ersetzung stattfindet, wenn sedsie ausgeführt werden:

find . \
    -type f \
    -exec grep -q 'Ms. Johnson' {} \; \
    -print0 |
xargs -0 sed -i'.bup' -e's/Ms. Johnson/Mrs. Melbin/g'

Wenn Sie wirklich schlau sein wollen, können Sie auf Folgendes verzichten find:

grep -Z -l -r 'Ms. Johnson' |
    xargs -0 sed -i'.bup' -e's/Ms. Johnson/Mrs. Melbin/g'
Amphetamachine
quelle
1
Ich glaube , Sie brauchen eine '{}'kurz vor Ihrer \;dass -exec.
Jander
8

Find hat einen -execOperator, der einen beliebigen Befehl ausführt. Noch besser -execist ein Test , mit dem Sie mehrere -execs miteinander verketten können. Wenn frühere Befehle fehlschlagen, werden spätere nicht ausgeführt. Die Zeichenfolge {}wird durch den aktuellen Dateinamen ersetzt und ;markiert das Ende des Befehls. Sie sollten beide angeben, um eine Störung der Shell zu vermeiden.

So:

find . -type f \
    -exec grep -q 'Ms. Johnson' '{}' \; \
    -exec sed -i'.bup' -e's/Ms. Johnson/Mrs. Melbin/g' '{}' \;
Jander
quelle
Ich hatte noch nie ein Problem mit der Verwendung von Bare's.
Amphetamachine
1
@amphetamachine: Ich auch nicht, aber die Manpage schlägt es vor. Es kann sich um eine csh-Sache handeln, oder es gibt Shells (nicht Bash und nicht POSIX), die {}bei der Klammererweiterung auf den Null-String expandieren.
Jander
4

Ich habe auf die Manpage geschaut und keine Möglichkeit gefunden, dies direkt zu tun sed, wie Sie es sicher getan haben, bevor Sie gefragt haben. Ich sehe verschiedene Möglichkeiten, um dies mit grep zu umgehen, aber ich denke, das ist das einfachste:

grep -rlZ "Ms. Johnson" . | xargs -0 sed -i'.bup' -e's/Ms. Johnson/Mrs. Melbin/g'

-rrecurse
-lprint filename endet nur
-Zmit null

Kevin
quelle
-1

Anscheinend müssen Sie zuerst eine Liste der Dateien erstellen, die Ihren Suchbegriffen entsprechen.

search="test"
for match in $(find -type f -exec grep -l $search "{}" \;)
do sed -i'.bup' -e "/$search/ s/$search/Mrs. Melbin/g" "$match"
done
laebshade
quelle
Sie haben das .als erstes Argument vergessen find. Auch, wie ich selbst vor nicht allzu langer Zeit informiert wurde, müssen Sie nicht wirklich zitieren oder dem{}
Kevin
Generieren Sie keine Liste von Dateien und führen Sie darauf keine Befehlsersetzung durch. Zuerst findwird eine durch Zeilenumbrüche getrennte Liste von Dateien erstellt, dann wird diese Liste von der Shell in Leerzeichen (nicht nur in Zeilenumbrüche) aufgeteilt, und anschließend wird jedes Wort von der Shell als Glob-Muster behandelt. Dies funktioniert nur, wenn Ihre Dateinamen keine Leerzeichen oder enthalten \[?*. Andere Antworten auf dieser Seite zeigen, wie dies richtig gemacht wird.
Gilles 'SO- hör auf böse zu sein'
@ Kevin Das .ist bei find unnötig (zumindest find unter Linux), da angenommen wird, dass kein Pfad ein Argument übergibt .
Laebshade