Lesen und Schreiben einer Datei: Befehl tee

10

Es ist bekannt, dass ein Befehl wie dieser:

cat filename | some_sed_command >filename

Löscht den Dateinamen der Datei, da die vor dem Befehl ausgeführte Umleitung der Ausgabe dazu führt, dass der Dateiname abgeschnitten wird.

Man könnte das Problem folgendermaßen lösen:

cat file | some_sed_command | tee file >/dev/null

Ich bin mir aber nicht sicher, ob dies auf jeden Fall funktionieren würde: Was passiert, wenn die Datei (und das Ergebnis des Befehls sed) sehr groß ist? Wie kann das Betriebssystem vermeiden, Inhalte zu überschreiben, die noch nicht gelesen werden? Ich sehe, dass es auch einen Schwammbefehl gibt, der auf jeden Fall funktionieren sollte: Ist er "sicherer" als Tee?

VeryHardCoder
quelle
Was ist dein Hauptziel? (in einfachen Worten)
Sergiy Kolodyazhnyy
@Serg verstehen einfach, wie die Dinge funktionieren ... Die Antwort von kos klärt die Sache
VeryHardCoder

Antworten:

10

Man könnte das Problem folgendermaßen lösen:

cat file | some_sed_command | tee file >/dev/null

Nein .

Die Chancen filewerden abgeschnitten, aber es gibt keine Garantie, cat file | some_sed_command | tee file >/dev/nulldass sie nicht abgeschnitten werden file.

Es hängt alles davon ab, welcher Befehl zuerst verarbeitet wird, im Gegensatz zu dem, was man erwarten kann, werden Befehle in einer Pipe nicht von links nach rechts verarbeitet . Es gibt keine Garantie dafür, welcher Befehl zuerst ausgewählt wird. Man kann ihn sich also genauso gut als zufällig ausgewählt vorstellen und sich niemals darauf verlassen, dass die Shell den beleidigenden Befehl nicht auswählt.

Da die Wahrscheinlichkeit, dass der fehlerhafte Befehl zuerst zwischen drei Befehlen ausgewählt wird, geringer ist als die Wahrscheinlichkeit, dass der fehlerhafte Befehl zuerst zwischen zwei Befehlen ausgewählt wird, ist es weniger wahrscheinlich, filedass er abgeschnitten wird, aber es wird trotzdem passieren .

script.sh::

#!/bin/bash
for ((i=0; i<100; i++)); do
    cat >file <<-EOF
    foo
    bar
    EOF
    cat file |
        sed 's/bar/baz/' |
        tee file >/dev/null
    [ -s file ] &&
        echo 'Not truncated' ||
        echo 'Truncated'
done |
    sort |
    uniq -c
rm file
% bash script.sh
 93 Not truncated
  7 Truncated
% bash script.sh
 98 Not truncated
  2 Truncated
% bash script.sh
100 Not truncated

Verwenden Sie also niemals so etwas cat file | some_sed_command | tee file >/dev/null. Verwenden Sie spongewie von Oli vorgeschlagen.

Alternativ kann man für dichtere Umgebungen und / oder relativ kleine Dateien eine Here-Zeichenfolge und eine Befehlsersetzung verwenden, um die Datei zu lesen, bevor ein Befehl ausgeführt wird:

$ cat file
foo
bar
$ for ((i=0; i<100; i++)); do <<<"$(<file)" sed 's/bar/baz/' >file; done
$ cat file
foo
baz
kos
quelle
9

Für sedspeziell, können Sie seine Verwendung -ian Ort und Stelle Argumente. Es wird nur in der geöffneten Datei gespeichert, z.

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

Wenn Sie etwas Stärkeres tun möchten, vorausgesetzt, Sie tun mehr als sed, ja, können Sie das Ganze mit sponge(aus dem moreutilsPaket) puffern, wodurch der gesamte Standard "aufgesaugt" wird, bevor Sie in die Datei schreiben. Es ist wie, teeaber mit weniger Funktionalität. Für die grundlegende Verwendung ist es jedoch so ziemlich ein Drop-In-Ersatz:

cat file | some_sed_command | sponge file >/dev/null

Ist das sicherer? Bestimmt. Es hat wahrscheinlich Grenzen durch. Wenn Sie also etwas Kolossales tun (und nicht direkt mit sed bearbeiten können), möchten Sie möglicherweise Ihre Änderungen an einer zweiten Datei vornehmen und mvdiese Datei dann wieder auf den ursprünglichen Dateinamen zurücksetzen. Das sollte atomar sein (damit alles, was von diesen Dateien abhängt, nicht kaputt geht, wenn sie ständigen Zugriff benötigen).

Oli
quelle
0

Sie können Vim im Ex-Modus verwenden:

ex -sc '%!some_sed_command' -cx filename
  1. % Wählen Sie alle Zeilen aus

  2. ! Führen Sie den Befehl aus

  3. x Speichern und Beenden

Steven Penny
quelle
0

Oh, aber spongeist nicht die einzige Option; Sie müssen nicht erhalten moreutils, damit dies richtig funktioniert. Jeder Mechanismus funktioniert, solange er die folgenden zwei Anforderungen erfüllt:

  1. Es akzeptiert den Namen der Ausgabedatei als Parameter.
  2. Die Ausgabedatei wird erst erstellt, wenn alle Eingaben verarbeitet wurden.

Sie sehen, das bekannte Problem, auf das sich das OP bezieht, ist, dass die Shell alle Dateien erstellt, die für die Arbeit der Pipes erforderlich sind, bevor überhaupt mit der Ausführung der Befehle in der Pipeline begonnen wird. Es ist also die Shell, die tatsächlich abschneidet Die Ausgabedatei (die leider auch die Eingabedatei ist), bevor einer der Befehle überhaupt ausgeführt werden konnte.

Der teeBefehl funktioniert nicht, obwohl er die erste Anforderung erfüllt, da er die zweite Anforderung nicht erfüllt: Er erstellt die Ausgabedatei immer sofort nach dem Start, sodass er im Wesentlichen so schlecht ist wie das Erstellen einer Pipe direkt in die Ausgabedatei. (Es ist tatsächlich schlimmer, weil seine Verwendung eine nicht deterministische zufällige Verzögerung einführt, bevor die Ausgabedatei abgeschnitten wird. Sie könnten also denken, dass es funktioniert, obwohl dies nicht der Fall ist.)

Alles, was wir brauchen, um dieses Problem zu lösen, ist ein Befehl, der alle Eingaben puffert, bevor eine Ausgabe erzeugt wird, und der den Ausgabedateinamen als Parameter akzeptieren kann, damit wir seine Ausgabe nicht weiterleiten müssen die Ausgabedatei. Ein solcher Befehl ist shuf. Folgendes wird also dasselbe bewirken sponge:

    shuf --output=file --random-source=/dev/zero 

Der --random-source=/dev/zeroTeil shufversucht, seine Sache zu erledigen, ohne überhaupt zu mischen, sodass er Ihre Eingabe puffert, ohne sie zu ändern.

Mike Nakis
quelle