Fügen Sie am Anfang und am Ende der riesigen Datei Zeilen hinzu


23

Ich habe das Szenario, in dem Zeilen am Anfang und Ende der riesigen Dateien hinzugefügt werden.

Ich habe versucht, wie unten gezeigt.

  • für die erste Zeile:

    sed -i '1i\'"$FirstLine" $Filename
  • für die letzte Zeile:

    sed -i '$ a\'"$Lastline" $Filename  

Das Problem bei diesem Befehl ist jedoch, dass die erste Zeile der Datei angehängt und die gesamte Datei durchlaufen wird. In der letzten Zeile wird erneut die gesamte Datei durchlaufen und eine letzte Zeile angehängt. Da es sich um eine sehr große Datei (14 GB) handelt, dauert dies sehr lange.

Wie kann ich am Anfang und am Ende einer Datei eine Zeile einfügen, während ich die Datei nur einmal lese?

Antworten:


20

sed -iVerwendet tempfiles als Implementierungsdetail. Dies ist das, was Sie gerade erleben. Wenn Sie jedoch Daten vor den Anfang eines Datenstroms stellen, ohne den vorhandenen Inhalt zu überschreiben, müssen Sie die Datei neu schreiben. Selbst wenn Sie dies vermeiden, können Sie dies nicht umgehen sed -i.

Wenn das Umschreiben der Datei keine Option ist, können Sie sie beim Lesen möglicherweise bearbeiten. Beispiel:

{ echo some prepended text ; cat file ; } | command

Sed dient auch zum Bearbeiten von Streams - eine Datei ist kein Stream. Verwenden Sie ein Programm wie ed oder ex, das für diesen Zweck vorgesehen ist. Die -iOption sed ist nicht nur nicht portierbar, sondern unterbricht auch alle Symlinks zu Ihrer Datei, da sie im Wesentlichen gelöscht und neu erstellt wird, was sinnlos ist.

Sie können dies mit einem einzigen Befehl edwie folgt tun:

ed -s file << 'EOF'
0a
prepend these lines
to the beginning
.
$a
append these lines
to the end
.
w
EOF

Beachten Sie, dass je nach Implementierung von ed möglicherweise eine Auslagerungsdatei verwendet wird, für die mindestens so viel Speicherplatz verfügbar sein muss.


Hallo, der Befehl ed, den Sie bereitgestellt haben, funktioniert sehr gut für große Dateien. Aber ich habe 3 große Dateien wie Test, Test1, Test 2. Ich gab Befehl wie ed -s Tes * << 'EOF' 0a voranstellen diese Zeilen an den Anfang. $ a füge diese Zeilen an das Ende an. w EOF Aber es wird nur eine Testdatei erstellt und die erste / letzte Zeile hinzugefügt. Wie können wir Änderungen in demselben Befehl vornehmen, sodass in allen Dateien die erste und die letzte Zeile hinzugefügt werden müssen?
UNIXbest

@UNIXbest - Verwenden Sie eine forSchleife:for file in Tes*; do [command]; done
Chris Down

Hi Down, ich habe unten Befehl für Datei in Tes * verwendet; do ed -s Tes * << 'EOF' 0a HEllO HDR. $ a Hallo TLR. w EOF done Aber es wird immer noch in die erste Datei geschrieben.
UNIXbest

Richtig, weil du das "$file"nicht Tes*als Argument verwenden musst ed.
Chris Down

2
@UNIXbest Wenn Ihr Problem durch diese Antwort gelöst wurde, sollten Sie in Betracht ziehen, sie zu akzeptieren.
Joseph R.

9

Beachten Sie, dass Sie Folgendes tun können, wenn Sie die Zuordnung einer vollständigen Kopie der Datei auf der Festplatte vermeiden möchten:

sed '
1i\
begin
$a\
end' < file 1<> file

Dies nutzt die Tatsache, dass, wenn es sich bei stdin / stdout um eine Datei handelt, sed blockweise gelesen und geschrieben wird. Hier ist es also in Ordnung, die gelesene Datei zu überschreiben, solange die erste hinzugefügte Zeile kleiner als seddie Blockgröße ist (sollte etwa 4k oder 8k sein).

Beachten Sie jedoch, dass wenn aus irgendeinem Grund ein sedFehler auftritt (Abbruch, Maschinenabsturz ...), die Datei zur Hälfte verarbeitet wird, was bedeutet, dass einige Daten der Größe der ersten Zeile irgendwo in der Mitte fehlen.

Beachten Sie auch , dass , wenn Ihr sedist die GNU sed, die nicht Arbeit für binäre Daten (aber da Sie verwenden -i, den Sie verwenden GNU sed).


Diese Fehler für mich auf Ubuntu 16.04
Csaba Toth

4

Hier sind einige Optionen (alle erstellen eine neue Kopie der Datei, stellen Sie also sicher, dass Sie genügend Speicherplatz dafür haben):

  • einfaches Echo / Katze

    echo "first" > new_file; cat $File >> new_file; \
      echo "last" >> new_file; 
  • awk / gawk usw

    gawk 'BEGIN{print "first\n"}{print}END{print "last\n"}' $File > NewFile 

    awkund sein Typ liest Dateien Zeile für Zeile. Der BEGIN{}Satz wird vor der ersten Zeile und der END{}Satz nach der letzten Zeile ausgeführt. Also, der obige Befehl bedeutet print "first" at the beginning, then print every line in the file and print "last" at the end.

  • Perl

    perl -ne 'BEGIN{print "first\n"} print;END{print "last\n"}' $File > NewFile

    Dies ist im Wesentlichen dasselbe wie der oben in Perl geschriebene Gawk.


1
Beachten Sie, dass Sie in all diesen Fällen mindestens 14 GB mehr Speicherplatz für die neue Datei benötigen.
Chris Down

@ ChrisDown guter Punkt, ich habe meine Antwort bearbeitet, um das klar zu machen. Ich nahm an, dass dies kein Problem war, da das OP verwendet wurde, sed -idas temporäre Dateien erstellt.
Terdon

3

Ich bevorzuge das viel einfachere:

gsed -i '1s/^/foo\n/gm; $s/$/\nbar/gm' filename.txt

Dies transformiert die Datei:

asdf
qwer

zur Datei:

foo
asdf
qwer
bar

2

Sie können Vim im Ex-Modus verwenden:

ex -sc '1i|ALFA' -c '$a|BRAVO' -cx file
  1. 1 erste Zeile auswählen

  2. i Text und Zeilenumbruch einfügen

  3. $ Letzte Zeile auswählen

  4. a Text und Zeilenumbruch anhängen

  5. x speichern und schließen


Was wäre, wenn wir dies für mehrere Dateien tun wollten?
Geoyws

1
@geoyws, das ist nicht wirklich im Bereich für diese Frage
Steven Penny

Sind Sie sicher, dass $ a und nicht% a ist?
Carlos Robles

2

Es gibt keine Möglichkeit, Daten am Anfang einer Datei einzufügen¹. Sie können lediglich eine neue Datei erstellen, die zusätzlichen Daten schreiben und die alten Daten anhängen. Sie müssen also mindestens einmal die gesamte Datei neu schreiben, um die erste Zeile einzufügen. Sie können die letzte Zeile jedoch anhängen, ohne die Datei neu zu schreiben.

sed -i '1i\'"$FirstLine" $Filename
echo "$LastLine" >>$Filename

Alternativ können Sie die beiden Befehle in einem Durchlauf von sed kombinieren.

sed -i -e '1i\'"$FirstLine" -e '$ a\'"$Lastline" $Filename

sed -iErstellt eine neue Ausgabedatei und verschiebt sie dann über die alte Datei. Dies bedeutet, dass während der Arbeit von sed eine zweite Kopie der Datei vorhanden ist, die Speicherplatz belegt. Sie können dies vermeiden, indem Sie die vorhandene Datei überschreiben , jedoch mit erheblichen Einschränkungen: Die hinzugefügte Zeile muss kleiner sein als der Puffer von sed. Wenn Ihr System abstürzt, gehen eine beschädigte Datei und einige Inhalte in der Datei verloren Mitte, also empfehle ich dringend dagegen.

¹ Linux kann zwar Daten in eine Datei einfügen, es können jedoch nur eine ganze Reihe von Dateisystemblöcken und keine Zeichenfolgen beliebiger Länge eingefügt werden. Es ist nützlich für einige Anwendungen, wie z. B. Datenbanken und virtuelle Maschinen, aber für Textdateien unbrauchbar.


Nicht wahr. Schauen Sie sich fallocate()mit FALLOC_FL_INSERT_RANGEverfügbaren auf XFS und ext4 in modernen Kerneln (4.xx) man7.org/linux/man-pages/man2/fallocate.2.html
Eric

@Eric Sie können nur ganze Blöcke einfügen, jedoch keine willkürlichen Bytelängen, zumindest ab Linux 4.15.0 mit ext4. Gibt es ein Dateisystem, das beliebige Bytelängen einfügen kann?
Gilles 'SO- hör auf böse zu sein'

Richtig, aber es macht Ihre Aussage immer noch nicht richtig. Sie haben geschrieben: "Es gibt keine Möglichkeit, Daten am Anfang einer Datei einzufügen". Das stimmt immer noch nicht: Es gibt einen Mechanismus zum Einfügen von Extents am Anfang einer Datei. Sicher, es gibt einige Einschränkungen, aber es ist erwähnenswert, da sich einige Benutzer möglicherweise nicht um die Blockgrößenbeschränkungen kümmern, indem sie Leerzeichen oder Zeilenumbrüche einfügen.
Eric

0
$ (echo "Some Text" ; cat file1) > file2

4
Nur Code Antwort sind nicht akzeptabel, bitte verbessern Sie Ihre Antwort
Networker

Erwägen Sie, Ihre Antwort um eine Erläuterung Ihres Vorschlags oder um Links zu Dokumentationen zu erweitern, die Ihre Lösung unterstützen.
HalosGhost

-1

Moderne Linux-Kernel (höher als 4.1 oder 4.2) unterstützen das Einfügen von Daten am Anfang einer Datei über den fallocate()Systemaufruf mit den FALLOC_FL_INSERT_RANGEDateisystemen ext4 und xfs. Im Wesentlichen ist dies eine logische Verschiebeoperation: Die Daten werden logisch mit einem höheren Versatz verschoben.

Es gibt eine Einschränkung hinsichtlich der Granularität des Bereichs, den Sie am Anfang der Datei einfügen möchten. Aber für Textdateien können Sie wahrscheinlich etwas mehr als erforderlich zuweisen (bis zur Granularitätsgrenze) und mit Leerzeichen oder Zeilenumbrüchen füllen, aber das hängt von Ihrer Anwendung ab

Ich kenne kein sofort verfügbares Linux-Dienstprogramm, das Dateierweiterungen manipuliert, aber es ist nicht schwierig zu schreiben: Besorgen Sie sich einen Dateideskriptor und rufen Sie fallocate()mit den entsprechenden Argumenten auf. Weitere Informationen finden Sie in der Manpage des fallocateSystemaufrufs: http://man7.org/linux/man-pages/man2/fallocate.2.html


Ein Dienstprogramm ist nicht das Problem (vorausgesetzt ein nicht eingebettetes Linux): util-linux enthält ein fallocateDienstprogramm. Das Problem ist, dass eine Granularität ganzer Blöcke dies für die meisten Textdateien unbrauchbar macht. Ein weiteres Problem besteht darin, dass die Bereichszuordnung und die anschließende Änderung nicht atomar sind. Das Problem ist hier also nicht wirklich gelöst.
Gilles 'SO- hör auf böse zu sein'

Die Granularität ist eine Einschränkung, die ich bereits erwähnt habe, und nein, sie macht sie nicht unbrauchbar, sondern hängt von der Anwendung ab. Wo haben Sie in der Frage gesehen, dass Atomizität wichtig ist? Ich kann nur das Problem der Aufführungen sehen. Trotzdem scheint dieser Syscall atomar zu sein: elixir.bootlin.com/linux/latest/source/fs/open.c#L228 und wenn die Atomarität wichtig wird (ist es nicht, aber sagen wir, es ist aus Gründen des Arguments) benutze einfach die Dateisperre. (Zeigen Sie mir auf die Stelle im Kernel-Code, an der die fallocateAtomizität gebrochen ist, bitte, ich bin neugierig)
Eric
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.