
Guten Abend,
Ich möchte den Inhalt einer Datei mit einigen Pipe-Befehlen filtern und das Ergebnis dann wieder in dieselbe Datei schreiben. Ich weiß, so wie ich es geschrieben habe, geht das nicht. Moment mal …
Dies ist das Bash-Skriptstück, das ich habe.
grep '^[a-zA-Z.:]' "$filepath" \
| sed -r '/^(rm|cd)/d' \
| uniq -u \
> "$filepath"
Also dachte ich, ich könnte es schaffen, wenn ich stattdessen Prozesssubstitution verwende. Dann schrieb ich:
grep '^[a-zA-Z.:]' < <(cat "$filepath") | …
Auch das hat nichts gelöst. Ich hatte erwartet, dass die Prozesssubstitution den Inhalt meiner Eingabedatei irgendwo „speichert“, beispielsweise in einer temporären Datei. Es scheint, als hätte ich die Prozesssubstitution auch nicht verstanden.
Ich habe Threads zur „Inplace“-Edition gelesen, aber in diesen Artikeln wurden spezielle Optionen einiger Binärdateien hervorgehoben, wie sed -i
oder sort -o
, aber ich brauche eine allgemeine Lösung (ich meine, sie muss für alle weitergeleiteten Befehle geeignet sein).
Also zunächst, warum kann das nicht mit der „Standardmethode von Pipes“ gemacht werden, was passiert dahinter? :/
Und wie soll ich mein Problem lösen? Könnte jemand bitteerklärenich, worum geht es hier?
Danke schön.
Antwort1
Wie bereits erwähnt, Schwamm ausmehrutilsist großartig. Ich verwende dieses Skript zur Emulation, um die Moreutils-Abhängigkeit zu vermeiden:
#!/bin/sh -e
#Soak up input and tee it to arguments
st=0; tmpf=
tmpf="`mktemp`" && exec 3<>"$tmpf" || st="$?"
rm -f "$tmpf" #remove it even if exec failed; noop if mktemp failed
[ "$st" = 0 ] || exit "$st"
cat >&3
</dev/fd/3 tee "$@" >/dev/null
Sie können es wie folgt verwenden:
grep '^[a-zA-Z.:]' "$filepath" \
| sed -r '/^(rm|cd)/d' \
| uniq -u | sponge "$filepath"
Mit einer einfachen Ausgabeumleitung ist dies nicht möglich, da die Umleitungen vor dem Starten der Befehle stattfinden und eine Ausgabeumleitung die Ausgabedatei abschneidet.
Mit anderen Worten: Bis grep (der erste einfache Befehl der Pipeline) startet, hat die letzte Umleitung die Eingabe-/Ausgabedatei bereits gekürzt.
Soweit ich weiß, gibt es keine Standard-UNIX-Dienstprogramme, die echte In-Place-Bearbeitung durchführen. sed -i
emuliert es nur mit einer temporären Datei. Ich vermute, der Grund dafür ist, dass echte In-Place-Filterung die Datei leicht beschädigen kann, wenn ein Pipeline-Schritt fehlschlägt.
Was darunter passiert, ist, dass sowohl Systempipes |
als <()
auch IOs verwendet werden, die jeweils einen Puffer für den Durchgang von IOs übernehmen. Der Mechanismus erstellt keine temporären Dateien (jedenfalls keine echten (Dateisystem-)Dateien) und versucht zu vermeiden, dass die gesamte Eingabe gleichzeitig im Speicher gehalten wird.
Antwort2
Wenn Sie die Eingabe aus und die Ausgabe in dieselbe Datei wünschen, können Sie Folgendes versuchen:Schwamm. In der Beschreibung heißt es:
sponge reads standard input and writes it out to the specified file.
Unlike a shell redirect, sponge soaks up all its input before writing
the output file. This allows constructing pipelines that read from and
write to the same file.
Sie können also so etwas wie sed '...' file | grep '...' | sponge [-a] file
die Eingabe vonDateiund Ausgabe an dieselbeDatei.
Andererseits ist die Verwendung temporärer Dateien auch eine gute Möglichkeit, mit derselben Datei für Eingabe und Ausgabe zu arbeiten. Sie können Ihre temporären Dateien wie folgt initialisieren:
tempfile=`mktemp tempFile.XXXX` # You can replace "tempFile" with any name you want
Dadurch wird in dem Verzeichnis, in dem dieses Skript ausgeführt wird, eine temporäre Datei mit dem Namen „tempFile“ und der Erweiterung „XXXX“ erstellt, wobei die x durch eine Kombination aus der aktuellen Prozessnummer und zufälligen Buchstaben ersetzt werden (z. B. tempFile.AVm7).
Jetzt können Sie Ihre Pipe (oder jeden beliebigen Pipe-Befehl) wie folgt ändern:
grep '^[a-zA-Z.:]' "$filepath" \
| sed -r '/^(rm|cd)/d' \
| uniq -u \
> "$tempfile"
Nach dem Filtern können Sie Ihre temporäre Datei wie folgt in Ihre Originaldatei verschieben:
mv "$tempfile" "$filepath"
Dadurch wird Ihre temporäre Datei gelöscht und Sie behalten die gefilterte Originaldatei. Manchmal erstellen Sie jedoch eine Menge temporärer Dateien, die Sie nicht benötigen und nicht gelöscht haben. Daher ist es eine gute Idee, Ihr Verzeichnis zu bereinigen, indem Sie alle temporären Dateien löschen, wenn Sie sie nach dem Ende Ihres Skripts nicht mehr benötigen. Sie können hierfür eine Routine wie folgt schreiben:
remove_temp_files() {
rm `find . -name "tempFile.????"`
}
Dann können Sie Ihre Routine einfach am Ende Ihres Skripts aufrufen remove_temp_files
und dabei alle temporären Dateien löschen, die im oben beschriebenen Format erstellt wurden.
Antwort3
Verwenden vonHier-DokumentUndBefehlsersetzungist in diesem Fall die Standardmethode:
grep '^[a-zA-Z.:]' <<IN \
| sed -r '/^(rm|cd)/d' \
| uniq -u \
> "$filepath"
$(cat -- "$filepath")
IN
Weitere Fragen wurden bereits in vielen Fragen erläutert: