sed edit file in place

300

Ich versuche herauszufinden, ob es möglich ist, eine Datei in einem einzigen sed-Befehl zu bearbeiten, ohne den bearbeiteten Inhalt manuell in eine neue Datei zu streamen und die neue Datei dann in den ursprünglichen Dateinamen umzubenennen. Ich habe die -iOption ausprobiert, aber mein Solaris-System hat angegeben, dass dies -ieine illegale Option ist. Gibt es einen anderen Weg?

amphibient
quelle
7
-iist eine Option in gnu sed, aber nicht in Standard sed. Der Inhalt wird jedoch in eine neue Datei gestreamt und anschließend umbenannt, sodass er nicht Ihren Wünschen entspricht.
William Pursell
2
Eigentlich ist es das, was ich will. Ich möchte nur nicht der alltäglichen Aufgabe ausgesetzt sein, die neue Datei in den ursprünglichen Namen
umzubenennen
3
Dann müssen Sie die Frage wiederholen.
William Pursell
3
@amphibient: Würde es Ihnen etwas ausmachen, dem Titel Ihrer Frage das Wort "Solaris" voranzustellen? Der Wert Ihrer Frage geht verloren. Bitte beachten Sie die Kommentare unter meiner Antwort. Vielen Dank.
Steve
1
@Steve: Ich habe das Solaris-Präfix wieder aus dem Titel entfernt, da dies keineswegs nur für Solaris gilt.
Tripleee

Antworten:

477

Die -iOption überträgt den bearbeiteten Inhalt in eine neue Datei und benennt ihn dann trotzdem hinter den Kulissen um.

Beispiel:

sed -i 's/STRING_TO_REPLACE/STRING_TO_REPLACE_IT/g' filename

und

sed -i '' 's/STRING_TO_REPLACE/STRING_TO_REPLACE_IT/g' filename

unter macOS .

Choroba
quelle
3
Zumindest tut es das für mich, damit ich es nicht muss
amphibient
2
Sie könnten dann versuchen, die auf Ihrem System sedierte GNU zu kompilieren.
Choroba
3
Beispiel:sed -i "s/STRING_TO_REPLACE/STRING_TO_REPLACE_IT/g" <file>
Thales Ceolin
2
Dies ist besser als die Perl-Lösung. Irgendwie erstellt es keine .swap-Datei .... danke!
Mathe
3
@maths: Ja, aber vielleicht woanders oder für eine kürzere Zeit?
Choroba
72

Auf einem System, auf dem sedDateien nicht bearbeitet werden können, ist meiner Meinung nach die bessere Lösung perl:

perl -pi -e 's/foo/bar/g' file.txt

Obwohl dadurch eine temporäre Datei erstellt wird, wird das Original ersetzt, da ein leeres Suffix / eine leere Erweiterung angegeben wurde.

Steve
quelle
14
Es wird nicht nur eine temporäre Datei erstellt, sondern es werden auch feste Verknüpfungen unterbrochen (z. B. anstatt den Inhalt der Datei zu ändern, wird die Datei durch eine neue Datei mit demselben Namen ersetzt.) Manchmal ist dies das gewünschte Verhalten, manchmal auch akzeptabel, aber wenn es mehrere Links zu einer Datei gibt, ist es fast immer das falsche Verhalten.
William Pursell
3
@ DwightSpencer: Ich glaube, dass alle Systeme ohne Perl kaputt sind. Ein einzelner Befehl übertrumpft eine Befehlskette, wenn man erhöhte Berechtigungen wünscht und verwenden muss sudo. In meinen Augen geht es darum, wie man das manuelle Erstellen und Umbenennen einer temporären Datei vermeidet, ohne die neueste GNU oder BSD installieren zu müssen sed. Verwenden Sie einfach Perl.
Steve
4
@PetrPeller: Wirklich? Wenn Sie die Frage sorgfältig lesen, werden Sie verstehen, dass das OP versucht, so etwas wie zu vermeiden sed 's/foo/bar/g' file.txt > file.tmp && mv file.tmp file.txt. Nur weil die direkte Bearbeitung die Umbenennung mithilfe einer temporären Datei vornimmt, bedeutet dies nicht, dass er / sie eine manuelle Umbenennung durchführen muss , wenn die Option nicht verfügbar ist. Es gibt andere Tools, die tun können, was er / sie will, und Perl ist die offensichtliche Wahl. Wie ist das keine Antwort auf die ursprüngliche Frage?
Steve
2
@a_arias: Du hast recht; er hat. Mit Solaris kann er jedoch nicht erreichen, was er will sed. Die Frage war eigentlich sehr gut, aber ihr Wert wurde von Leuten gemindert, die entweder keine Manpages lesen können oder sich nicht die Mühe machen, hier auf SO tatsächlich Fragen zu lesen.
Steve
4
@EdwardGarson: IIRC, Solaris wird mit Perl ausgeliefert, jedoch nicht mit Python oder Ruby. Und wie ich bereits sagte, kann das OP mit Solaris sed nicht das gewünschte Ergebnis erzielen.
Steve
56

Beachten Sie, dass unter OS X möglicherweise seltsame Fehler wie "ungültiger Befehlscode" oder andere seltsame Fehler auftreten, wenn Sie diesen Befehl ausführen. Um dieses Problem zu beheben, versuchen Sie es

sed -i '' -e "s/STRING_TO_REPLACE/STRING_TO_REPLACE_IT/g" <file>

Dies liegt daran, dass in der OSX-Version von seddie -iOption ein extensionArgument erwartet , sodass Ihr Befehl tatsächlich als extensionArgument analysiert und der Dateipfad als Befehlscode interpretiert wird. Quelle: https://stackoverflow.com/a/19457213

Diadyne
quelle
1
Dies ist eine weitere Kuriosität von OS X. Glücklicherweise können Sie mit a brew install gnu-sedzusammen mit a PATHdie GNU-Version von sed verwenden. Vielen Dank für die Erwähnung; ein definitiver Kopfkratzer.
Doug
23

Das Folgende funktioniert gut auf meinem Mac

sed -i.bak 's/foo/bar/g' sample

Wir ersetzen foo durch bar in der Beispieldatei. Die Sicherung der Originaldatei wird in sample.bak gespeichert

Verwenden Sie den folgenden Befehl, um Inline ohne Backup zu bearbeiten

sed -i'' 's/foo/bar/g' sample
minhas23
quelle
Wie kann verhindert werden, dass Sicherungsdateien aufbewahrt werden?
Petr Peller
@PetrPeller, Sie können den genannten Befehl für das gleiche Beispiel verwenden ... sed -i '' s / foo / bar / g '
minhas23
18

Eine Sache zu beachten, sedkann Dateien nicht alleine schreiben, da der einzige Zweck von sed darin besteht, als Editor für den "Stream" zu fungieren (dh Pipelines von stdin, stdout, stderr und anderen >&nPuffern, Sockets und dergleichen). In diesem Sinne können Sie einen anderen Befehl verwenden tee, um die Ausgabe zurück in die Datei zu schreiben. Eine andere Möglichkeit besteht darin, einen Patch zu erstellen, aus dem der Inhalt weitergeleitet wird diff.

Tee-Methode

sed '/regex/' <file> | tee <file>

Patch-Methode

sed '/regex/' <file> | diff -p <file> /dev/stdin | patch

AKTUALISIEREN:

Beachten Sie außerdem, dass durch Patch die Datei von Zeile 1 der Diff-Ausgabe geändert wird:

Patch muss nicht wissen, auf welche Datei zugegriffen werden soll, da sich diese in der ersten Zeile der Ausgabe von diff befindet:

$ echo foobar | tee fubar

$ sed 's/oo/u/' fubar | diff -p fubar /dev/stdin
*** fubar   2014-03-15 18:06:09.000000000 -0500
--- /dev/stdin  2014-03-15 18:06:41.000000000 -0500
***************
*** 1 ****
! foobar
--- 1 ----
! fubar

$ sed 's/oo/u/' fubar | diff -p fubar /dev/stdin | patch
patching file fubar
Dwight Spencer
quelle
2
/dev/stdin 2014-03-15, antwortete 7. Januar - bist du ein Zeitreisender?
Adrian Frühwirth
Unter Windows ist die Verwendung von msysgit /dev/stdinnicht vorhanden, daher müssen Sie /dev/stdineinen einzelnen Bindestrich ohne Anführungszeichen durch '-' ersetzen , damit die folgenden Befehle funktionieren: $ sed 's/oo/u/' fubar | diff -p fubar -und$ sed 's/oo/u/' fubar | diff -p fubar - | patch
Jim Raden
@ AdrianFrühwirth Ich habe irgendwo eine blaue Polizeikiste und aus irgendeinem Grund nennen sie mich den Doktor bei der Arbeit. Aber ehrlich gesagt sieht es so aus, als ob es zu diesem Zeitpunkt eine wirklich schlechte Zeitverschiebung im System gibt.
Dwight Spencer
Diese Lösungen beruhen für mich auf einem bestimmten Pufferverhalten. Ich vermute, dass bei größeren Dateien diese beschädigt werden können, da während des Lesens von sed die Datei möglicherweise durch den Patch-Befehl oder noch schlimmer durch den Tee-Befehl, der die Datei abschneidet, darunter geändert wird. Eigentlich bin ich mir nicht sicher, ob patchich nicht auch abschneiden werde. Ich denke, das Speichern der Ausgabe in einer anderen Datei und das anschließende catEinfügen in das Original wäre sicherer.
Akostadinov
echte In-Place-Antwort von @ William-Pursell
Akostadinov
11

Es ist nicht möglich zu tun, was Sie wollen sed. Sogar Versionen seddavon unterstützen die -iOption zum Bearbeiten einer vorhandenen Datei und tun genau das, was Sie ausdrücklich angegeben haben, dass Sie nicht möchten: Sie schreiben in eine temporäre Datei und benennen die Datei dann um. Aber vielleicht können Sie einfach verwenden ed. Um beispielsweise alle Vorkommen von footo barin der Datei zu ändern file.txt, haben Sie folgende Möglichkeiten:

echo ',s/foo/bar/g; w' | tr \; '\012' | ed -s file.txt

Die Syntax ist ähnlich sed, aber sicherlich nicht genau gleich.

Aber vielleicht sollten Sie überlegen, warum Sie keine umbenannte temporäre Datei verwenden möchten. Selbst wenn Sie keine -iUnterstützung haben sed, können Sie einfach ein Skript schreiben, um die Arbeit für Sie zu erledigen. Stattdessen sed -i 's/foo/bar/g' filekönnten Sie tun inline file sed 's/foo/bar/g'. Ein solches Skript ist trivial zu schreiben. Zum Beispiel:

#!/bin/sh -e
IN=$1
shift
trap 'rm -f $tmp' 0
tmp=$( mktemp )
<$IN "$@" >$tmp && cat $tmp > $IN  # preserve hard links

sollte für die meisten Anwendungen ausreichend sein.

William Pursell
quelle
1
Wie würden Sie dieses Skript beispielsweise benennen und wie würden Sie es aufrufen?
Amphibient
2
Ich würde es nennen inlineund es wie oben beschrieben aufrufen:inline inputfile sed 's/foo/bar/g'
William Pursell
1
Wow, ednur eine Antwort, die wirklich an Ort und Stelle ist. Zum ersten Mal brauche ich jemals ed. Danke dir. Eine kleine Optimierung ist zu verwenden echo -e ",s/foo/bar/g\012 w"oder echo $',s/foo/bar/g\012 w'wo Shell es erlaubt, zusätzliche trAnrufe zu vermeiden .
Akostadinov
2
edmacht die Änderungen nicht wirklich an Ort und Stelle. Aus der straceAusgabe ederstellt GNU eine temporäre Datei, schreibt die Änderungen in die temporäre Datei und schreibt dann die gesamte Originaldatei neu, wobei feste Links erhalten bleiben. Bei der trussAusgabe verwendet Solaris 11 edauch eine temporäre Datei, benennt die temporäre Datei jedoch beim Speichern mit dem wBefehl in den ursprünglichen Dateinamen um, wodurch alle festen Links zerstört werden.
Andrew Henle
@ AndrewHenle Das ist entmutigend. Das ed, das mit aktuellen Macos geliefert wird (Mojave, was auch immer dieser Marketing-Jargon bedeutet), behält weiterhin Hardlinks bei. YMMV
William Pursell
10

Sie könnten vi verwenden

vi -c '%s/foo/bar/g' my.txt -c 'wq'
mench
quelle
9

sed unterstützt die direkte Bearbeitung. Von man sed:

-i[SUFFIX], --in-place[=SUFFIX]

    edit files in place (makes backup if extension supplied)

Beispiel :

Angenommen, Sie haben eine Datei hello.txtmit dem Text:

hello world!

Wenn Sie eine Sicherungskopie der alten Datei erstellen möchten, verwenden Sie:

sed -i.bak 's/hello/bonjour' hello.txt

Sie erhalten zwei Dateien: hello.txtmit dem Inhalt:

bonjour world!

und hello.txt.bakmit dem alten Inhalt.

Wenn Sie keine Kopie behalten möchten, übergeben Sie einfach nicht den Erweiterungsparameter.

Sergio A.
quelle
3
Das OP erwähnt ausdrücklich, dass seine Plattform diese (beliebte, aber) nicht standardmäßige Option nicht unterstützt.
Tripleee
3

Sie haben nicht angegeben, welche Shell Sie verwenden, aber mit zsh können Sie das =( )Konstrukt verwenden, um dies zu erreichen. Etwas in der Art von:

cp =(sed ... file; sync) file

=( )ähnelt, >( )erstellt jedoch eine temporäre Datei, die beim cpBeenden automatisch gelöscht wird .

Thor
quelle
3
mv file.txt file.tmp && sed 's/foo/bar/g' < file.tmp > file.txt

Sollte alle Hardlinks beibehalten, da die Ausgabe zurückgeleitet wird, um den Inhalt der Originaldatei zu überschreiben, und die Notwendigkeit einer speziellen Version von sed vermieden wird.

JJM
quelle
3

Wenn Sie die gleiche Anzahl von Zeichen ersetzen und nach dem sorgfältigen Lesen der "In-Place" -Bearbeitung von Dateien ...

Sie können auch den Umleitungsoperator verwenden <>, um die Datei zum Lesen und Schreiben zu öffnen:

sed 's/foo/bar/g' file 1<> file

Sehen Sie es live:

$ cat file
hello
i am here                           # see "here"
$ sed 's/here/away/' file 1<> file  # Run the `sed` command
$ cat file
hello
i am away                           # this line is changed now

Aus dem Bash-Referenzhandbuch → 3.6.10 Öffnen von Dateideskriptoren zum Lesen und Schreiben :

Der Umleitungsoperator

[n]<>word

bewirkt, dass die Datei, deren Name die Erweiterung des Wortes ist, sowohl zum Lesen als auch zum Schreiben im Dateideskriptor n oder im Dateideskriptor 0 geöffnet wird, wenn n nicht angegeben ist. Wenn die Datei nicht vorhanden ist, wird sie erstellt.

fedorqui 'SO hör auf zu schaden'
quelle
Dies durch Datei beschädigt. Ich bin mir nicht sicher, warum das so ist. paste.fedoraproject.org/462017
Nehal J Wani
Siehe Zeilen [43-44], [88-89], [135-136]
Nehal J Wani,
2
Dieser Blogpost erklärt, warum diese Antwort nicht verwendet werden sollte. backreference.org/2011/01/29/in-place-editing-of-files
Nehal J Wani
@NehalJWani Ich werde den Artikel lange lesen, danke für das Heads-up! Was ich in der Antwort nicht erwähnt habe, ist, dass dies funktioniert, wenn die gleiche Anzahl von Zeichen geändert wird.
Fedorqui 'SO hör auf zu verletzen'
@NehalJWani dies gesagt, es tut mir leid, wenn meine Antwort Schaden an Ihren Dateien verursacht hat. Ich werde es mit Korrekturen aktualisieren (oder bei Bedarf löschen), nachdem ich die von Ihnen angegebenen Links gelesen habe.
Fedorqui 'SO hör auf zu verletzen'
2

Wie Moneypenny in Skyfall sagte: "Manchmal sind die alten Wege am besten." Kincade sagte später etwas Ähnliches.

$ printf ',s/false/true/g\nw\n' | ed {YourFileHere}

Viel Spaß beim Bearbeiten. '\ Nw \ n' hinzugefügt, um die Datei zu schreiben. Entschuldigung für die verspätete Beantwortung der Anfrage.

jlettvin
quelle
Können Sie erklären, was dies bewirkt?
Matt Montag
1
Es ruft ed (1) auf, den Texteditor mit einigen in stdin geschriebenen Zeichen. Als jemand unter 30 denke ich, dass es in Ordnung ist zu sagen, dass ed (1) für Dinosaurier ist, wie Dinosaurier für uns. Anstatt ed zu öffnen und das Zeug einzutippen, leitet jlettvin die Zeichen ein |. @ MattMontag
Ari Sweedler
Wenn Sie fragen, was die Befehle tun, gibt es zwei davon, die durch a beendet werden \n. [range] s / <old> / <new> / <flag> ist die Form des Ersatzbefehls - ein Paradigma, das noch in vielen anderen Texteditoren zu sehen ist (okay, vim, ein direkter Nachkomme von ed). Wie auch immer, die gFlagge steht für "global" und bedeutet "jede Instanz in jeder Zeile, die Sie betrachten". Der Bereich umfasst die 3,5Zeilen 3-5. ,5schaut auf StartOfFile-5, 3,schaut auf die Zeilen 3-EndOfFile und ,schaut auf StartOfFile-EndOfFile. Ein globaler Ersetzungsbefehl in jeder Zeile, der "false" durch "true" ersetzt. Dann wird der Schreibbefehl weingegeben.
Ari Sweedler
1

Sehr gute Beispiele. Ich hatte die Herausforderung, viele Dateien an Ort und Stelle zu bearbeiten, und die Option -i scheint die einzig vernünftige Lösung zu sein, die sie im Befehl find verwendet. Hier das Skript zum Hinzufügen von "version:" vor der ersten Zeile jeder Datei:

find . -name pkg.json -print -exec sed -i '.bak' '1 s/^/version /' {} \;
Robert
quelle