Frage Linux: Wie kann man eine Datei gleichzeitig als Eingabe und Ausgabe verwenden?


Ich habe gerade folgendes in bash ausgeführt:

uniq .bash_history > .bash_history

und meine Geschichte Datei endete völlig leer.

Ich denke, ich brauche eine Möglichkeit, die ganze Datei zu lesen, bevor ich darauf schreibe. Wie ist das gemacht?

PS: Ich dachte natürlich an eine temporäre Datei, aber ich suche eine elegantere Lösung.


45
2018-04-24 18:32


Ursprung


Das liegt daran, dass die Dateien von rechts nach links geöffnet werden. Siehe auch stackoverflow.com/questions/146435/... - kaerast
Sie müssen die Ausgabe in eine neue Datei im selben Verzeichnis schreiben und diese über der alten Datei umbenennen. Jeder andere Ansatz riskiert den Verlust Ihrer Daten, wenn sie auf halbem Wege unterbrochen wird. Einige Werkzeuge können diesen Schritt vor Ihnen verbergen. - kasperd
Oder, bash fügt aufeinanderfolgende Duplikate nicht in seine Historie ein, wenn Sie HISTCONTROL so einstellen, dass er Ignoredups enthält; Siehe die Manpage. - dave_thompson_085


Antworten:


Ich empfehle die Verwendung sponge von mehr. Von der Manpage:

DESCRIPTION
  sponge  reads  standard  input  and writes it out to the specified file. Unlike
  a shell redirect, sponge soaks up all its input before opening the output file.
  This allows for constructing pipelines that read from and write to the same 
  file.

Um dies auf Ihr Problem anzuwenden, versuchen Sie Folgendes:

uniq .bash_history | sponge .bash_history

41
2018-04-24 22:46



Es ist wie Katze, aber mit Saugfähigkeiten: D - MilliaLover


Ich wollte nur eine andere Antwort geben, die einfach ist und keinen Schwamm verwendet (weil es oft nicht in leichten Umgebungen enthalten ist).

echo "$(uniq .bash_history)" > .bash_history

sollte das gewünschte Ergebnis haben. Die Subshell wird ausgeführt, bevor .bash_history zum Schreiben geöffnet wird. Wie in der Antwort von Phil P erklärt, wurde .bash_history im ursprünglichen Befehl gelesen und bereits durch den Operator '>' abgeschnitten.


60
2017-10-20 08:46



Ich bin normalerweise kein Fan von Antworten, die eine uralte Frage aufwerfen, die bereits eine gültige, akzeptierte Antwort hat - aber das ist elegant, gut geschrieben und macht ein starkes Argument für seine Notwendigkeit (leichte Umgebungen); für mich fügt es wirklich etwas zu den vorhandenen Antworten hinzu. Willkommen bei SF, Hart (du warst schon einen Monat hier, aber ich denke, das ist dein erster inhaltlicher Beitrag). Ich hoffe, mehr Antworten von Ihnen zu lesen, wie dieser! - MadHatter
Dies ist die beste Lösung. Ich musste eine Subshell verwenden $() anstelle von Backticks wegen einiger flüchtiger Probleme. - CMCDragonkai
Ich frage mich, ob diese Lösung zu großen Dateien skaliert, sagen wir ... 20 oder 50 GB. - Amit Naidu
Das sollte wirklich die Antwort sein. - maxywb
Genau. Ich habe die angenommene Antwort markiert, also wird hoffentlich jemand es außer Kraft setzen. - Ced


Das Problem besteht darin, dass Ihre Shell die Befehlspipeline vor dem Ausführen der Befehle einrichtet. Es geht nicht um "Ein- und Ausgabe", sondern darum, dass der Inhalt der Datei schon weg ist, bevor uniq überhaupt ausgeführt wird. Es geht ungefähr so:

  1. Die Schale öffnet die > Ausgabedatei zum Schreiben, abschneiden
  2. Die Shell richtet sich so ein, dass der Datei-Deskriptor 1 (für stdout) für diese Ausgabe verwendet wird
  3. Die Shell führt uniq aus, vielleicht etwas wie execlp ("uniq", "uniq", ".bash_history", NULL)
  4. uniq läuft, öffnet .bash_history und findet dort nichts

Es gibt verschiedene Lösungen, einschließlich der In-Place-Bearbeitung und der temporären Dateinutzung, die andere erwähnen, aber der Schlüssel ist, das Problem zu verstehen, was eigentlich schief läuft und warum.


11
2018-04-24 23:36





Verwende den Schwamm von mehr

uniq .bash_history | sponge .bash_history

6
2018-04-24 22:46





Ein weiterer Trick, dies zu tun, ohne zu verwenden sponge, ist der folgende Befehl:

{ rm .bash_history && uniq > .bash_history; } < .bash_history

Dies ist einer der Cheats, die in dem ausgezeichneten Artikel beschrieben werden "In-Place" Bearbeitung von Dateien auf backreference.org.

Es öffnet im Grunde die Datei zum Lesen, dann "entfernt" es. Es wird jedoch nicht wirklich entfernt: Es gibt einen offenen Dateideskriptor, der darauf zeigt, und solange das geöffnet bleibt, ist die Datei immer noch vorhanden. Dann erstellt es eine neue Datei mit dem gleichen Namen und schreibt die eindeutigen Zeilen darauf.

Nachteil dieser Lösung: Wenn uniq scheitert aus irgendeinem Grund, Ihre Geschichte wird weg sein.


6
2017-11-26 15:49





Diese sed Skript entfernt benachbarte Duplikate. Mit dem -i Option, es macht die Änderung an Ort und Stelle. Es ist von der sed  info Datei:

sed -i 'h;:b;$b;N;/^\(.*\)\n\1$/ {g;bb};$b;P;D' .bash_history

3
2018-04-24 19:39



sed verwendet immer noch die temporäre Datei und fügte eine Antwort mit hinzu strace Illustration (nicht dass es wirklich wichtig ist) :-) - Kyle Brandt♦
@ Kyle: True, aber "außer Sicht, aus dem Sinn". Persönlich würde ich die explizite temporäre Datei seit so etwas wie process input > tmp && mv tmp input ist viel einfacher und lesbarer als die Verwendung sed trickery einfach um eine temporäre Datei zu vermeiden und es wird nicht mein Original überschrieben, wenn es fehlschlägt (ich weiß nicht, ob sed -i scheitert anmutig - ich würde es aber denken). Außerdem gibt es eine Menge Dinge, die Sie mit der Ausgabe-zu-Temp-Datei-Methode tun können, die nicht direkt durchgeführt werden kann, ohne dass etwas noch mehr involviert ist sed Skript. Ich weiß, dass du das alles weißt, aber es könnte einigen Zuschauern zugute kommen. - Dennis Williamson


Als interessanter Leckerbissen verwendet sed auch eine temporäre Datei (das macht es nur für dich):

$ strace sed -i 's/foo/bar/g' foo    
open("foo", O_RDONLY|O_LARGEFILE)       = 3
...
open("./sedPmPv9z", O_RDWR|O_CREAT|O_EXCL|O_LARGEFILE, 0600) = 4
...
read(3, "foo\n"..., 4096)               = 4
write(4, "bar\n"..., 4)                 = 4
read(3, ""..., 4096)                    = 0
close(3)                                = 0
close(4)                                = 0
rename("./sedPmPv9z", "foo")            = 0
close(1)                                = 0
close(2)                                = 0

Beschreibung:
Die temporäre Datei ./sedPmPv9z wird fd 4, und die foo Dateien werden FD 3. Die Leseoperationen sind auf FD3 und die Schreibvorgänge auf FD4 (die temporäre Datei). Die foo-Datei wird dann beim Umbenennungsaufruf mit der temporären Datei überschrieben.


3
2018-04-25 01:12





Eine temporäre Datei ist es ziemlich viel, es sei denn, der Befehl befriedigte zufällig in der Bearbeitung (uniq nicht - einige seds machen (sed -i)).


0
2018-04-24 18:45