Ich habe eine ziemlich große Datei (35 GB) und möchte diese Datei in situ filtern (dh ich habe nicht genügend Speicherplatz für eine andere Datei). Insbesondere möchte ich einige Muster prüfen und ignorieren - gibt es eine Möglichkeit dazu? Tun Sie dies, ohne eine andere Datei zu verwenden?
Angenommen, ich möchte alle Zeilen herausfiltern, die foo:
zum Beispiel Folgendes enthalten:
Antworten:
Auf der Systemaufrufebene sollte dies möglich sein. Ein Programm kann Ihre Zieldatei zum Schreiben öffnen, ohne sie abzuschneiden, und mit dem Schreiben beginnen, was von stdin gelesen wird. Beim Lesen von EOF kann die Ausgabedatei abgeschnitten werden.
Da Sie Zeilen aus der Eingabe herausfiltern, sollte die Schreibposition der Ausgabedatei immer kleiner als die Leseposition sein. Das heißt, Sie sollten Ihre Eingabe nicht mit der neuen Ausgabe verfälschen.
Ein Programm zu finden, das dies tut, ist jedoch das Problem.
dd(1)
hat die Option,conv=notrunc
dass die Ausgabedatei beim Öffnen nicht abgeschnitten wird, aber am Ende auch nicht abgeschnitten wird und der ursprüngliche Dateiinhalt nach dem grep-Inhalt erhalten bleibt (mit einem Befehl wiegrep pattern bigfile | dd of=bigfile conv=notrunc
)Da es aus Sicht des Systemaufrufs sehr einfach ist, habe ich ein kleines Programm geschrieben und es auf einem kleinen (1 MB) Full-Loopback-Dateisystem getestet. Es hat getan, was Sie wollten, aber Sie möchten dies unbedingt zuerst mit einigen anderen Dateien testen. Es wird immer riskant sein, eine Datei zu überschreiben.
überschreiben.c
Sie würden es verwenden als:
Ich poste dies meistens, damit andere es kommentieren können, bevor Sie es versuchen. Vielleicht kennt jemand anderes ein Programm, das etwas Ähnliches macht, das mehr getestet wurde.
quelle
grep
nicht mehr Daten ausgegeben werden, als gelesen werden, sollte die Schreibposition immer hinter der Leseposition liegen. Auch wenn Sie mit der gleichen Geschwindigkeit wie das Lesen schreiben, ist dies in Ordnung. Versuchen Sie rot13 mit diesem anstelle von grep und dann noch einmal. md5sum das Vorher und Nachher und du wirst es gleich sehen.dd
, aber es ist umständlich.Sie können
sed
Dateien an Ort und Stelle bearbeiten (dies erstellt jedoch eine temporäre Zwischendatei):So entfernen Sie alle Zeilen, die Folgendes enthalten
foo
:So behalten Sie alle Zeilen bei, die Folgendes enthalten
foo
:quelle
$HOME
wird beschreibbar sein, sondern/tmp
wird nur gelesen werden (Standardeinstellung). Wenn Sie beispielsweise Ubuntu verwenden und die Wiederherstellungskonsole gestartet haben, ist dies normalerweise der Fall. Auch der Here-Document-Operator<<<
funktioniert dort nicht, da er Schreib- / Lesezugriff erfordert/tmp
, da er auch eine temporäre Datei in diese Datei schreibt. (vgl. diese Frage inkl. Ausgabe)strace
Ich gehe davon aus, dass es sich bei Ihrem Filterbefehl um einen so genannten Prefix-Shrinking-Filter handelt , der die Eigenschaft hat, dass Byte N in der Ausgabe niemals geschrieben wird, bevor mindestens N Byte der Eingabe gelesen wurden.
grep
hat diese Eigenschaft (solange sie nur filtert und keine anderen Aktionen wie das Hinzufügen von Zeilennummern für Übereinstimmungen ausführt). Mit einem solchen Filter können Sie die Eingabe währenddessen überschreiben. Natürlich müssen Sie darauf achten, keine Fehler zu machen, da der überschriebene Teil am Anfang der Datei für immer verloren geht.Die meisten Unix-Tools bieten nur die Möglichkeit, eine Datei anzuhängen oder zu kürzen, ohne dass die Möglichkeit besteht, sie zu überschreiben. Die einzige Ausnahme in der Standard-Toolbox ist
dd
, dass die Ausgabedatei nicht gekürzt werden soll. Der Plan ist also, den Befehl zu filterndd conv=notrunc
. Die Größe der Datei ändert sich dadurch nicht. Daher ermitteln wir auch die Länge des neuen Inhalts und kürzen die Datei auf diese Länge (erneut mitdd
). Beachten Sie, dass diese Aufgabe von Natur aus nicht robust ist. Wenn ein Fehler auftritt, sind Sie auf sich allein gestellt.Sie können rau äquivalentes Perl schreiben. Hier ist eine schnelle Implementierung, die nicht versucht, effizient zu sein. Natürlich können Sie Ihre anfängliche Filterung auch direkt in dieser Sprache durchführen.
quelle
Mit jeder Bourne-ähnlichen Shell:
Aus irgendeinem Grund scheinen die Leute den 40-jährigen und standardmäßigen Lese- und Schreibumleitungsoperator zu vergessen .
Wir öffnen
bigfile
in Lese + Schreibmodus und (was am wichtigsten ist hier) ohne Abschneiden auf ,stdout
währendbigfile
geöffnet ist (separat) aufcat
‚sstdin
. Nachdemgrep
beendet wurde und wenn es einige Linien entfernt hat, zeigtstdout
jetzt irgendwo innerhalbbigfile
, müssen wir loswerden, was jenseits dieses Punktes ist. Daher derperl
Befehl, der die Datei (truncate STDOUT
) an der aktuellen Position abschneidet (wie von zurückgegebentell STDOUT
).(das
cat
ist für GNUgrep
, das sich sonst beschwert, wenn stdin und stdout auf dieselbe Datei verweisen).¹ Nun, obwohl
<>
es von Anfang an in den späten siebziger Jahren in der Bourne-Shell war, war es zunächst undokumentiert und nicht richtig implementiert . Es war nicht in der ursprünglichen Implementierungash
von 1989 enthalten und obwohl es ein POSIX-sh
Redirection-Operator ist (seit Anfang der 90er Jahre, da POSIXsh
immer darauf basiertksh88
), wurde essh
erst im Jahr 2000 zu FreeBSD hinzugefügt , also portabel für 15 Jahre alt ist wahrscheinlich genauer. Beachten Sie auch, dass der Standard-Dateideskriptor, wenn er nicht angegeben ist,<>
in allen Shells enthalten ist, mit der Ausnahme, dassksh93
er 2010 in ksh93t + von 0 in 1 geändert wurde (Verstoß gegen die Abwärtskompatibilität und POSIX-Kompatibilität).quelle
perl -e 'truncate STDOUT, tell STDOUT'
? Es funktioniert für mich, ohne das einzuschließen. Gibt es eine Möglichkeit, dasselbe zu erreichen, ohne Perl zu verwenden?redirection "<>" fixed and documented (used in /etc/inittab f.i.).
das ist ein Hinweis.Auch wenn dies eine alte Frage ist, scheint es mir eine mehrjährige Frage zu sein, und es gibt eine allgemeinere, klarere Lösung als bisher vorgeschlagen. Kredit, bei dem der Kredit fällig ist: Ich bin mir nicht sicher, ob ich darauf gekommen wäre, ohne Stéphane Chazelas 'Erwähnung des
<>
Update-Betreibers in Betracht zu ziehen .Das Öffnen einer Datei zur Aktualisierung in einer Bourne-Shell ist von begrenztem Nutzen. Mit der Shell können Sie nicht nach einer Datei suchen und auch nicht die neue Länge festlegen (wenn sie kürzer als die alte ist). Aber das ist leicht zu beheben, so leicht bin ich überrascht, dass es nicht zu den Standard-Dienstprogrammen in gehört
/usr/bin
.Das funktioniert:
Wie das geht (Hutspitze an Stéphane):
(Ich verwende GNU grep. Vielleicht hat sich etwas geändert, seit er seine Antwort geschrieben hat.)
Es sei denn, Sie haben kein / usr / bin / ftruncate . Ein paar Dutzend Zeilen von C können Sie unten sehen. Dieses Dienstprogramm ftruncate schneidet einen beliebigen Dateideskriptor auf eine beliebige Länge ab, wobei die Standardausgabe und die aktuelle Position standardmäßig verwendet werden.
Der obige Befehl (1. Beispiel)
T
zum Aktualisieren. Genau wie bei open (2) positioniert das Öffnen der Datei auf diese Weise den aktuellen Offset auf 0.T
normal verarbeitet und die Shell leitet ihre AusgabeT
über den Deskriptor 4 weiter.Die Subshell wird dann beendet und der Deskriptor 4 geschlossen. Hier ist ftruncate :
Hinweis: ftruncate (2) ist bei dieser Verwendung nicht portierbar. Um die absolute Allgemeinheit zu gewährleisten, lesen Sie das letzte geschriebene Byte, öffnen Sie die Datei O_WRONLY erneut, suchen Sie, schreiben Sie das Byte und schließen Sie sie.
Angesichts der Tatsache, dass die Frage 5 Jahre alt ist, werde ich sagen, dass diese Lösung nicht naheliegend ist. Es nutzt exec , um einen neuen Deskriptor und den
<>
Operator zu öffnen, die beide geheimnisvoll sind. Ich kann nicht an ein Standarddienstprogramm denken, das einen Inode durch Dateideskriptor manipuliert. (Die Syntax könnte lautenftruncate >&4
, aber ich bin mir nicht sicher, ob es eine Verbesserung gibt.) Sie ist erheblich kürzer als die kompetente, explorative Antwort von camh. Es ist nur ein bisschen klarer als das von Stéphane, IMO, es sei denn, Sie mögen Perl mehr als ich. Ich hoffe, jemand findet es nützlich.Eine andere Möglichkeit wäre eine ausführbare Version von lseek (2), die den aktuellen Offset ausgibt. Die Ausgabe könnte für / usr / bin / truncate verwendet werden , was einige Linuxi bereitstellen.
quelle
ed
ist wahrscheinlich die richtige Wahl, um eine Datei direkt zu bearbeiten:quelle
ed
Versionen verhalten sich unterschiedlich ..... dies ist ausman ed
(GNU Ed 1.4) ...If invoked with a file argument, then a copy of file is read into the editor's buffer. Changes are made to this copy and not directly to file itself.
ed
keine Lösung für die Bearbeitung von 35-GB-Dateien ist, da die Datei in einen Puffer eingelesen wird.!
) akzeptieren kann , sodass möglicherweise noch ein paar weitere interessante Tricks aufed
die Datei abschneidet und neu schreibt. Dies ändert also nicht die Daten auf der Festplatte an Ort und Stelle, wie es das OP wünscht. Es kann auch nicht funktionieren, wenn die Datei zu groß ist, um in den Speicher geladen zu werden.Sie können einen Bash-Lese- / Schreib-Dateideskriptor verwenden, um Ihre Datei zu öffnen (um sie vor Ort zu überschreiben),
sed
undtruncate
... aber Sie dürfen natürlich niemals zulassen, dass Ihre Änderungen die bisher gelesene Datenmenge überschreiten .Hier ist das Skript (verwendet: Bash-Variable $ BASHPID)
Hier ist die Testausgabe
quelle
Ich würde die Datei mit einem Memory Map versehen, alles an Ort und Stelle mit char * -Pointern auf den nackten Speicher machen, dann die Zuordnung der Datei aufheben und sie abschneiden.
quelle
Nicht gerade vor Ort, aber - dies könnte unter ähnlichen Umständen von Nutzen sein.
Wenn der Speicherplatz ein Problem darstellt, komprimieren Sie zuerst die Datei (da dies Text ist, wird dies zu einer enormen Reduzierung führen) und verwenden Sie dann sed (oder grep oder was auch immer) auf die übliche Weise in der Mitte einer Dekomprimierungs- / Komprimierungspipeline.
quelle
sed -e '/foo/d' MyFile | gzip -c >MyEditedFile.gz && gzip -dc MyEditedFile.gz >MyFile
Zum Nutzen aller, die diese Frage googeln, ist die richtige Antwort, nicht mehr nach undurchsichtigen Shell-Funktionen zu suchen, die eine Beschädigung Ihrer Datei für einen vernachlässigbaren Leistungsgewinn riskieren, und stattdessen eine Variation dieses Musters zu verwenden:
Nur in der äußerst ungewöhnlichen Situation, dass dies aus irgendeinem Grund nicht durchführbar ist, sollten Sie ernsthaft über eine der anderen Antworten auf dieser Seite nachdenken (obwohl sie sicherlich interessant zu lesen sind). Ich werde zugeben, dass das Rätsel des OP, keinen Speicherplatz zum Erstellen einer zweiten Datei zu haben, genau eine solche Situation ist. Obwohl es auch dann noch andere Optionen gibt, z. B. @Ed Randall und @Basile Starynkevitch.
quelle
echo -e "$(grep pattern bigfile)" >bigfile
quelle
grepped
Daten die in der Befehlszeile angegebene Länge überschreiten. es verfälscht dann die Daten