Wie führe ich einen Befehl aus, der seine Datei (Argument) "in place" mit bash bearbeitet?


110

Ich habe eine Datei temp.txt, die ich mit dem sortBefehl in bash sortieren möchte .

Ich möchte, dass die sortierten Ergebnisse die Originaldatei ersetzen.

Dies funktioniert zum Beispiel nicht (ich bekomme eine leere Datei):

sortx temp.txt > temp.txt

Kann dies in einer Zeile erfolgen, ohne auf temporäre Dateien kopieren zu müssen?


EDIT: Die -oOption ist sehr cool für sort. Ich habe sortin meiner Frage als Beispiel verwendet. Ich habe das gleiche Problem mit anderen Befehlen:

uniq temp.txt > temp.txt.

Gibt es eine bessere allgemeine Lösung?


Antworten:


171
sort temp.txt -o temp.txt

3
Dies ist eine Antwort. Ich habe mich tatsächlich gefragt, ob es eine generische Lösung für dieses Problem gibt. Wenn ich beispielsweise alle UNIQ-Zeilen in einer Datei "an Ort und Stelle" finden möchte, kann ich -o
jm

Es ist nicht generisch, aber Sie können -u mit GNU-Sortierung verwenden, um eindeutige Zeilen zu finden
James

Hat jemand das Problem gelöst, um zB zuzulassen sort --inplace *.txt? Das wäre verrückt cool
sehe

@sehe Versuchen Sie dies:find . -name \*.txt -exec sort {} -o {} \;
Keith Gaughan

29

A sortmuss alle Eingaben sehen, bevor die Ausgabe beginnen kann. Aus diesem Grund kann das sortProgramm problemlos eine Option zum Ändern einer vorhandenen Datei anbieten:

sort temp.txt -o temp.txt

Im Einzelnen heißt es in der Dokumentation von GNUsort :

Normalerweise liest sort alle Eingaben, bevor die Ausgabedatei geöffnet wird, sodass Sie eine Datei mithilfe von Befehlen wie sort -o F Fund sicher sortieren können cat F | sort -o F. Jedoch sortmit --merge( -m) kann die Ausgabedatei öffnen , bevor alle Eingaben zu lesen, so dass ein Befehl wie cat F | sort -m -o F - Gnicht sicher ist , wie Art des Schreiben beginnen könnte , Fbevor catgetan wird es zu lesen.

Während die Dokumentation von BSD sortsagt:

Wenn [die] Ausgabedatei eine der Eingabedateien ist, kopiert sort sie in eine temporäre Datei, bevor die Ausgabe sortiert und in die [die] Ausgabedatei geschrieben wird.

Befehle wie uniqkönnen mit dem Schreiben der Ausgabe beginnen, bevor sie das Lesen der Eingabe beendet haben. Diese Befehle unterstützen normalerweise keine direkte Bearbeitung (und es wäre schwieriger für sie, diese Funktion zu unterstützen).

Normalerweise umgehen Sie dies mit einer temporären Datei. Wenn Sie unbedingt eine Zwischendatei vermeiden möchten, können Sie das gesamte Ergebnis in einem Puffer speichern, bevor Sie es ausschreiben. Zum Beispiel mit perl:

uniq temp.txt | perl -e 'undef $/; $_ = <>; open(OUT,">temp.txt"); print OUT;'

Hier liest der Perl-Teil die gesamte Ausgabe von uniqin Variable $_und überschreibt dann die Originaldatei mit diesen Daten. Sie können dasselbe in der Skriptsprache Ihrer Wahl tun, vielleicht sogar in Bash. Beachten Sie jedoch, dass genügend Speicher zum Speichern der gesamten Datei erforderlich ist. Dies ist bei der Arbeit mit großen Dateien nicht ratsam.


19

Hier ist ein allgemeinerer Ansatz, arbeitet mit Uniq, Sort und so weiter.

{ rm file && uniq > file; } < file

14
Ein weiterer generischer Ansatz mit spongeden moreutils : cat file |frobnicate |sponge file.
Tobu

3
@Tobu: Warum nicht als separate Antwort einreichen?
Flimm

1
Es ist wahrscheinlich gut zu beachten, dass dadurch nicht unbedingt die Dateiberechtigungen erhalten bleiben. Ihre Umask bestimmt die neuen Berechtigungen.
wor

1
Ein schwieriges. Können Sie erklären, wie es genau funktioniert?
patryk.beza

2
@ patryk.beza: In der Reihenfolge: Die Eingabe FD wird aus der Originaldatei geöffnet; Der ursprüngliche Verzeichniseintrag wird gelöscht. Die Umleitung wird verarbeitet, wodurch eine neue leere Datei mit demselben Namen wie die alte erstellt wird. dann wird der Befehl ausgeführt.
Charles Duffy

10

Tobus Kommentar zu Schwamm rechtfertigt eine eigenständige Antwort.

Um von der moreutils- Homepage zu zitieren :

Das wahrscheinlich allgemeinste Werkzeug in moreutils ist Schwamm (1), mit dem Sie folgende Dinge tun können:

% sed "s/root/toor/" /etc/passwd | grep -v joey | sponge /etc/passwd

Leidet jedoch spongeunter dem gleichen Problem, das Steve Jessop hier kommentiert. Wenn einer der Befehle in der Pipeline zuvor spongefehlschlägt, wird die Originaldatei überschrieben.

$ mistyped_command my-important-file | sponge my-important-file
mistyped-command: command not found

Oh, my-important-fileist weg.


1
Sponge weiß, dass es zum Ersetzen der Eingabedatei verwendet wird, und erstellt zunächst eine temporäre Datei, um eine Race-Bedingung zu vermeiden. Damit dies funktioniert, muss der Schwamm das letzte Element in der Pipeline sein und die Ausgabedatei selbst erstellen dürfen (im Gegensatz zum Beispiel zur Ausgabeumleitung auf Shell-Ebene). Übrigens: Es scheint eine einfache Lösung für den Quellcode für den Fall 'Fail' zu sein, die temporäre Datei im Falle eines Pipefails nicht umzubenennen (ich weiß nicht, warum Sponge diese Option nicht hat).
Brent Bradburn

Ich denke, wenn Sie set -o pipefailam Anfang Ihres Skripts hinzufügen , würde der Fehler am mistyped_command my-important-filedazu führen, dass das Skript sofort vor der Ausführung beendet wird sponge, wodurch die wichtige Datei erhalten bleibt.
Elouan Keryell-Even

6

Los geht's, eine Zeile:

sort temp.txt > temp.txt.sort && mv temp.txt.sort temp.txt

Technisch gesehen gibt es kein Kopieren in eine temporäre Datei, und der Befehl 'mv' sollte sofort ausgeführt werden.


6
Hm. Ich würde temp.txt.sort immer noch als temporäre Datei bezeichnen.
JesperE

5
Dieser Code ist riskant, da das Original überschrieben wird, wenn die Sortierung aus irgendeinem Grund fehlschlägt, ohne den Auftrag abzuschließen.
Steve Jessop

1
Der Mangel an Speicherplatz ist eine plausible Ursache oder ein Signal (Benutzer drückt STRG-C).
Steve Jessop

5
Wenn Sie so etwas verwenden möchten, verwenden Sie && (logisch und) anstelle von; Wenn Sie dies verwenden, wird sichergestellt, dass ein Befehl, der als nächstes fehlschlägt, nicht ausgeführt wird. Beispiel: cp backup.tar /root/backup.tar && rm backup.tar Wenn Sie keine Kopierrechte haben, sind Sie sicher, da die Datei nicht gelöscht wird
Daniels

1
änderte meine Antwort, um Ihre Vorschläge zu berücksichtigen, danke
davr

4

Ich mag die sort file -o fileAntwort, möchte aber nicht zweimal denselben Dateinamen eingeben.

Verwenden der BASH- Verlaufserweiterung :

$ sort file -o !#^

Nimmt das erste Argument der aktuellen Zeile, wenn Sie drücken enter.

Eine einzigartige Sorte vor Ort:

$ sort -u -o file !#$

greift nach dem letzten Argument in der aktuellen Zeile.


3

Viele haben die Option -o erwähnt . Hier ist der Manpage-Teil.

Von der Manpage:

   -o output-file
          Write output to output-file instead of to the  standard  output.
          If  output-file  is  one of the input files, sort copies it to a
          temporary file before sorting and writing the output to  output-
          file.

3

Dies wäre stark speicherbeschränkt, aber Sie könnten awk verwenden, um die Zwischendaten im Speicher zu speichern und sie dann wieder auszuschreiben.

uniq temp.txt | awk '{line[i++] = $0}END{for(j=0;j<i;j++){print line[j]}}' > temp.txt

Ich denke, es ist möglich, dass die >Datei abgeschnitten wird, bevor der Befehl ( uniqin diesem Fall) sie liest.
Martin

3

Eine Alternative zu spongeden häufigeren sed:

sed -ni r<(command file) file

Es funktioniert für jeden Befehl ( sort, uniq, tac, ...) und verwendet die sehr gut bekannt sedist -iOption (Bearbeiten von Dateien in-place).

Warnung: Versuchen Sie es command filezuerst, da das Bearbeiten von Dateien von Natur aus nicht sicher ist.


Erläuterung

Erstens Sie sagen sednicht die (original) Linien (drucken -nOption ), und mit Hilfe des sed‚s rKommandos und bash‘ s - Prozess Substitution , der erzeugte Inhalt durch <(command file)wird die Ausgabe gespeichert werden anstelle .


Die Dinge noch einfacher machen

Sie können diese Lösung in eine Funktion einbinden:

ip_cmd() { # in place command
    CMD=${1:?You must specify a command}
    FILE=${2:?You must specify a file}
    sed -ni r<("$CMD" "$FILE") "$FILE"
}

Beispiel

$ cat file
d
b
c
b
a

$ ip_cmd sort file
$ cat file
a
b
b
c
d

$ ip_cmd uniq file
$ cat file
a
b
c
d

$ ip_cmd tac file
$ cat file
d
c
b
a

$ ip_cmd
bash: 1: You must specify a command
$ ip_cmd uniq
bash: 2: You must specify a file

1

Verwenden Sie das Argument --output=oder-o

Gerade auf FreeBSD ausprobiert:

sort temp.txt -otemp.txt

Obwohl richtig, ist es einfach ein Duplikat dieser Antwort
whoan

1

uniqWas sind die Nachteile, um die Funktion hinzuzufügen :

sort inputfile | uniq | sort -o inputfile


0

Wenn Sie darauf bestehen, das sortProgramm zu verwenden, müssen Sie eine Zwischendatei verwenden - ich glaube, es sortgibt keine Option zum Sortieren im Speicher. Jeder andere Trick mit stdin / stdout schlägt fehl, es sei denn, Sie können garantieren, dass die Puffergröße für stdin von sort groß genug ist, um in die gesamte Datei zu passen.

Edit: Schande über mich. sort temp.txt -o temp.txtfunktioniert hervorragend.


Ich las das Q auch als "an Ort und Stelle", aber die zweite Lesung ließ mich glauben, dass er nicht wirklich danach fragte
Epatel

0

Eine andere Lösung:

uniq file 1<> file

Es sollte jedoch beachtet werden, dass der <>Trick nur in diesem Fall funktioniert, da uniqer nur Eingabezeilen in Ausgabezeilen kopiert und einige auf dem Weg fallen lässt. Wenn anderer Befehl (zB sed) verwendet , die die Eingabe ändern würde (zB würde jede Änderung ain aa), dann kann es außer Kraft setzt fileauf eine Weise , die keinen Sinn und sogar Schleife unendlich machen, vorausgesetzt , dass der Eingang ausreichend groß ist (mehr als ein einzelner Lesepuffer).
David
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.