Warum wird beim Umleiten der Ausgabe einer Datei an sich selbst eine leere Datei erstellt?

19

Warum wird beim Umleiten der Ausgabe einer Datei an sich selbst eine leere Datei erstellt?

In Bash, warum?

less foo.txt > foo.txt

und

fold foo.txt > foo.txt

ein leeres produzieren foo.txt? Da durch einen Anhang wie beispielsweise less eggs.py >> eggs.pyzwei Kopien des Texts erstellt werden eggs.py, kann davon ausgegangen werden, dass durch ein Überschreiben eine Kopie des Texts erstellt wird.

Beachten Sie, ich sage nicht, dass dies ein Fehler ist, es ist eher ein Hinweis auf etwas Tiefes über Unix.

Seewalker
quelle
Adressiert in U & Ls Canonical Was sind die Steuerungs- und Umleitungsoperatoren der Shell? Frage.
Scott,

Antworten:

20

Bei Verwendung von >wird die Datei im Kürzungsmodus geöffnet, sodass der Inhalt entfernt wird, bevor der Befehl versucht, sie zu lesen.

Wenn Sie verwenden >>, wird die Datei im Anhänge-Modus geöffnet, damit die vorhandenen Daten erhalten bleiben. Es ist jedoch immer noch sehr riskant, in diesem Fall dieselbe Datei als Eingabe- und Ausgabedatei zu verwenden. Wenn die Datei groß genug ist, um nicht in die Größe des Lese-Eingabepuffers zu passen, kann sie unbegrenzt größer werden, bis das Dateisystem voll ist (oder Ihr Datenträgerkontingent erreicht ist).

Wenn Sie eine Datei sowohl als Eingabe als auch als Ausgabe mit einem Befehl verwenden möchten, der keine direkte Änderung unterstützt, können Sie einige Problemumgehungen verwenden:

  • Verwenden Sie eine Zwischendatei und überschreiben Sie die Originaldatei, wenn Sie fertig sind und wenn beim Ausführen des Dienstprogramms kein Fehler aufgetreten ist (dies ist die sicherste und gebräuchlichste Methode).

    fold foo.txt > fold.txt.$$ && mv fold.txt.$$ foo.txt
  • Vermeiden Sie die Zwischenablage auf Kosten eines möglichen teilweisen oder vollständigen Datenverlusts, falls ein Fehler oder eine Unterbrechung auftritt. In diesem Beispiel wird der Inhalt von foo.txtals Eingabe an eine Subshell (in Klammern) übergeben, bevor die Datei gelöscht wird. Der vorherige Inode bleibt am Leben, da die Subshell ihn geöffnet hält, während Daten gelesen werden. Die Datei, die vom inneren Dienstprogramm (hier fold) mit demselben Namen geschrieben wurde (foo.txt) verweist auf einen anderen Inode, da der alte Verzeichniseintrag technisch so entfernt wurde, dass es während des Vorgangs zwei verschiedene "Dateien" mit demselben Namen gibt. Wenn die Subshell endet, wird der alte Inode freigegeben und seine Daten gehen verloren. Achten Sie darauf, dass Sie genügend Speicherplatz haben, um die alte und die neue Datei gleichzeitig zwischenzuspeichern. Andernfalls gehen Daten verloren.

    (rm foo.txt; fold > foo.txt) < foo.txt
jlliagre
quelle
3
spongevon moreutils kann auch helfen. fold foo.txt | sponge foo.txt- oder fold foo.txt | sponge !$sollte auch tun.
Slhck
@slhck Tatsächlich könnte Schwamm die Arbeit auch erledigen. Da es jedoch in Unix-ähnlichen Betriebssystemen weder von POSIX noch vom Mainstream spezifiziert wird, ist es unwahrscheinlich, dass es vorhanden ist.
Juli
Es ist nicht , wie es nicht sein kann gemacht vorhanden obwohl;)
slhck
7

Die Datei wird von der Shell zum Schreiben geöffnet, bevor die Anwendung sie lesen kann. Das Öffnen der Datei zum Schreiben schneidet sie ab.

Ignacio Vazquez-Abrams
quelle
0

In der Bash ... > foo.txtleert sich der Stream-Umleitungsoperator, foo.txt bevor der linke Operand ausgewertet wird .

Man könnte die Befehlsersetzung verwenden und das Ergebnis als Workaround ausdrucken. Diese Lösung benötigt weniger zusätzliche Zeichen als in anderen Antworten:

printf "%s\n" "$(less foo.txt)" > foo.txt

Achtung: Dieser Befehl behält keine Zeilenumbrüche bei foo.txt. Weitere Informationen finden Sie im Kommentarbereich unten

Hier wird die Subshell vor dem Stream-Redirection-Operator $(...)ausgewertet , wodurch die Informationen erhalten bleiben .>

Louis-Jacob Lebel
quelle
@KamilMaciorowski: Eigentlich gibt es tmp=$(cmd; printf q);  printf '%s' "${tmp%q}". Sie haben jedoch ein anderes Problem mit dieser Antwort verpasst: Es heißt "Subshell", wenn es "Befehlsersetzung" bedeutet. Ja, Befehlsersetzungen sind im Allgemeinen Subshells, aber nicht umgekehrt, und Subshells sind im Allgemeinen keine Hilfe für dieses Problem.
Scott,
@KamilMaciorowski Ich fühle mich so schlecht, weil ich das alles verpasst habe. Vielen Dank für den Hinweis. Für Ihren (4.) Punkt: Würden Backquotes den Trick machen, dh nachfolgende Zeilenumbrüche beibehalten?
Louis-Jacob Lebel
@ Scott danke für deine antwort. Ich habe "subshell" für "command substitution" geändert. Ich frage mich übrigens, was genau der Unterschied zwischen den beiden ist.
Louis-Jacob Lebel
Nein, Backquotes (Backticks) entfernen auch Zeilenumbrüche.
Kamil Maciorowski
Also gut, ich habe jetzt eine Warnmeldung hinzugefügt. Ich werde es entfernen, wenn ich eine Lösung finde.
Louis-Jacob Lebel