Entfernen Sie effizient die letzten beiden Zeilen einer extrem großen Textdatei

24472
Russ Bradberry

Ich habe eine sehr große Datei (~ 400 GB) und muss die letzten 2 Zeilen daraus entfernen. Ich versuchte es zu benutzen sed, aber es lief stundenlang, bevor ich aufgab. Gibt es einen schnellen Weg, oder bleibe ich dabei sed?

31
Sie können den GNU-Kopf ausprobieren. "head -n -2 file" user31894 vor 14 Jahren 6
Es wurden einige Perl- und Java-Vorschläge für eine Zeile in http://stackoverflow.com/questions/2580335/matlab-delete-elements-of-binary-fileswithout-loading-entire-file angegeben mtrw vor 14 Jahren 0

12 Antworten auf die Frage

31
Dennis Williamson

Ich habe dies nicht an einer großen Datei versucht, um zu sehen, wie schnell es ist, aber es sollte ziemlich schnell sein.

So verwenden Sie das Skript zum Entfernen von Zeilen am Ende einer Datei:

./shorten.py 2 large_file.txt 

Es sucht bis zum Ende der Datei, prüft, ob das letzte Zeichen ein Zeilenumbruch ist, liest dann jedes Zeichen einzeln und rückwärts, bis es drei Zeilenumbrüche gefunden hat, und schneidet die Datei unmittelbar nach diesem Punkt ab. Die Änderung wird vorgenommen.

Edit: Ich habe unten eine Python 2.4 Version hinzugefügt.

Hier ist eine Version für Python 2.5 / 2.6:

#!/usr/bin/env python2.5 from __future__ import with_statement # also tested with Python 2.6  import os, sys  if len(sys.argv) != 3: print sys.argv[0] + ": Invalid number of arguments." print "Usage: " + sys.argv[0] + " linecount filename" print "to remove linecount lines from the end of the file" exit(2)  number = int(sys.argv[1]) file = sys.argv[2] count = 0  with open(file,'r+b') as f: f.seek(0, os.SEEK_END) end = f.tell() while f.tell() > 0: f.seek(-1, os.SEEK_CUR) char = f.read(1) if char != '\n' and f.tell() == end: print "No change: file does not end with a newline" exit(1) if char == '\n': count += 1 if count == number + 1: f.truncate() print "Removed " + str(number) + " lines from end of file" exit(0) f.seek(-1, os.SEEK_CUR)  if count < number + 1: print "No change: requested removal would leave empty file" exit(3) 

Hier ist eine Python 3-Version:

#!/usr/bin/env python3.0  import os, sys  if len(sys.argv) != 3: print(sys.argv[0] + ": Invalid number of arguments.") print ("Usage: " + sys.argv[0] + " linecount filename") print ("to remove linecount lines from the end of the file") exit(2)  number = int(sys.argv[1]) file = sys.argv[2] count = 0  with open(file,'r+b', buffering=0) as f: f.seek(0, os.SEEK_END) end = f.tell() while f.tell() > 0: f.seek(-1, os.SEEK_CUR) print(f.tell()) char = f.read(1) if char != b'\n' and f.tell() == end: print ("No change: file does not end with a newline") exit(1) if char == b'\n': count += 1 if count == number + 1: f.truncate() print ("Removed " + str(number) + " lines from end of file") exit(0) f.seek(-1, os.SEEK_CUR)  if count < number + 1: print("No change: requested removal would leave empty file") exit(3) 

Hier ist eine Python 2.4 Version:

#!/usr/bin/env python2.4  import sys  if len(sys.argv) != 3: print sys.argv[0] + ": Invalid number of arguments." print "Usage: " + sys.argv[0] + " linecount filename" print "to remove linecount lines from the end of the file" sys.exit(2)  number = int(sys.argv[1]) file = sys.argv[2] count = 0 SEEK_CUR = 1 SEEK_END = 2  f = open(file,'r+b') f.seek(0, SEEK_END) end = f.tell()  while f.tell() > 0: f.seek(-1, SEEK_CUR) char = f.read(1) if char != '\n' and f.tell() == end: print "No change: file does not end with a newline" f.close() sys.exit(1) if char == '\n': count += 1 if count == number + 1: f.truncate() print "Removed " + str(number) + " lines from end of file" f.close() sys.exit(0) f.seek(-1, SEEK_CUR)  if count < number + 1: print "No change: requested removal would leave empty file" f.close() sys.exit(3) 
Auf unserem System wird Python 2.4 ausgeführt, und ich bin nicht sicher, ob einer unserer Dienste davon abhängt. Wird dies in diesem Fall funktionieren? Russ Bradberry vor 14 Jahren 0
@Russ: Ich habe eine Version für Python 2.4 hinzugefügt. Dennis Williamson vor 14 Jahren 0
Einfach unglaublich! funktionierte wie ein Zauber und in weniger als einer Sekunde! Russ Bradberry vor 14 Jahren 1
12
user31894

you can try GNU head

head -n -2 file 
Es ist die beste Lösung, da es einfach ist. xiao vor 12 Jahren 0
Dies zeigt ihm die letzten zwei Zeilen der Datei an, aber entfernt sie nicht aus seiner Datei. An meinem System funktioniert nicht einmal head: Illegale Zeilenanzahl - -2 SooDesuNe vor 12 Jahren 1
@SooDesuNe: Nein, es werden alle Zeilen vom Anfang bis zu 2 Zeilen vom Ende gedruckt, wie im Handbuch beschrieben. Dies müsste jedoch in eine Datei umgeleitet werden, und dann gibt es das Problem, dass diese Datei riesig ist, und ist daher nicht die perfekte Lösung für dieses Problem. Daniel Andersson vor 12 Jahren 2
+1 Warum wird dies nicht als richtige Antwort akzeptiert? Es ist schnell, einfach und funktioniert wie erwartet. aefxx vor 12 Jahren 0
@DanielAndersson Warum nicht? Sie können "head -n -2 file> output" ... mreq vor 11 Jahren 0
@PetrMarek und andere: Das Problem war, dass es sich um eine _giant_-Datei handelte. Diese Lösung erfordert, dass die gesamte Datei durch eine Pipe geleitet wird und alle Daten an einen neuen Speicherort geschrieben werden. Der Kernpunkt der Frage ist, dies zu vermeiden. Es ist eine In-Place-Lösung erforderlich, z. B. die in der akzeptierten Antwort. Daniel Andersson vor 11 Jahren 6
7
timday

I see my Debian Squeeze/testing systems (but not Lenny/stable) include a "truncate" command as part of the "coreutils" package.

With it you could simply do something like

truncate --size=-160 myfile 

to remove 160 bytes from the end of the file (obviously you need to figure out exactly how many characters you need to remove).

Dies ist die schnellste Route, da die Datei direkt geändert wird. Daher müssen Sie die Datei weder kopieren noch analysieren. Sie müssen jedoch immer noch prüfen, wie viele Bytes entfernt werden müssen ... Ich denke, dass ein einfaches `dd`-Skript dies tun wird (Sie müssen den Eingangsversatz angeben, um den letzten Kilobyte zu erhalten, und dann 'tail -2' verwenden | LANG = wc -c` oder sowas). liori vor 14 Jahren 0
Ich benutze CentOS, also habe ich nicht gekürzt. Genau das suche ich jedoch. Russ Bradberry vor 14 Jahren 0
`tail` ist auch für große Dateien effizient - kann` tail | wc -c`, um die Anzahl der zu trimmenden Bytes zu berechnen. krlmlr vor 8 Jahren 0
6
Zac Thompson

Das Problem bei sed ist, dass es sich um einen Stream-Editor handelt. Er verarbeitet die gesamte Datei, auch wenn Sie nur am Ende Änderungen vornehmen möchten. Egal was, Sie erstellen eine neue 400 GB-Datei, Zeile für Zeile. Jeder Editor, der die gesamte Datei bearbeitet, hat wahrscheinlich dieses Problem.

Wenn Sie die Anzahl der Zeilen kennen, können Sie verwenden head, aber dies erstellt erneut eine neue Datei, anstatt die vorhandene an Ort und Stelle zu ändern. Ich denke, Sie können Geschwindigkeitsgewinne durch die Einfachheit der Aktion erzielen.

Sie könnte mehr Glück mit splitder Datei in kleinere Stücke zu brechen, die letzte Bearbeitung, und dann mit catwieder, sie zu kombinieren, aber ich bin nicht sicher, ob es nicht besser sein. Ich würde Byte-Zählungen anstelle von Zeilen verwenden, sonst wird es wahrscheinlich nicht schneller sein - Sie werden immer noch eine neue 400-GB-Datei erstellen.

2
leeand00

Versuchen Sie VIM ... Ich bin nicht sicher, ob es den Trick tut oder nicht, da ich es noch nie für eine so große Datei verwendet habe, aber ich habe es bei kleineren Dateien verwendet, die es in der Vergangenheit versucht haben.

Ich glaube zwar, dass vim nur das lädt, was sofort beim ** Bearbeiten ** um den Puffer geht, jedoch habe ich keine Ahnung, wie er speichert. Phoshi vor 14 Jahren 0
vim hängt, während versucht wird, die Datei zu laden Russ Bradberry vor 14 Jahren 0
Nun, wenn es hängt, ah, warte darauf. Beginnen Sie mit dem Laden, gehen Sie zur Arbeit, kommen Sie nach Hause und sehen Sie, ob es fertig ist. leeand00 vor 14 Jahren 0
Siehe hierzu: http://stackoverflow.com/questions/159521/text-editor-to-open-big-giant-huge-large-text-files leeand00 vor 14 Jahren 2
1
Blackbeagle

Welche Art von Datei und in welchem ​​Format? Je nach Art der Datei kann es einfacher sein, etwas wie Perl zu verwenden - Text, Grafiken, Binärdateien? Wie ist es formatiert - CSV, TSV ...

Es handelt sich hierbei um formatierten Pipe-begrenzten Text, jedoch sind die letzten 2 Zeilen jeweils eine Spalte, die meinen Import bricht, sodass ich sie entfernen muss Russ Bradberry vor 14 Jahren 0
Ist das Fixieren des "Imports" für diesen Fall eine Option? timday vor 14 Jahren 0
nein der Import ist das infobright "load data infile" Russ Bradberry vor 14 Jahren 0
1
timday

If you know the size of the file to the byte (400000000160 say) and you know that you need to remove exactly 160 characters to strip the last two lines, then something like

dd if=originalfile of=truncatedfile ibs=1 count=400000000000 

should do the trick. It's been ages since I used dd in anger though; I seem to remember things go faster if you use a bigger block size, but whether you can do that depends on whether the lines you want to drop are at a nice multiple.

dd has some other options to pad text records out to a fixed size which might be useful as a preliminary pass.

Ich habe es versucht, aber es lief ungefähr so ​​schnell wie Sed. Es hatte ungefähr 200 MB in 10 Minuten geschrieben. Bei dieser Geschwindigkeit würde es buchstäblich hunderte von Stunden dauern, bis der Vorgang abgeschlossen war. Russ Bradberry vor 14 Jahren 0
1
timday

If "truncate" command isn't available on your system (see my other answer), look at the "man 2 truncate" for the system call to truncate a file to a specified length.

Obviously you need to know how many characters you need to truncate the file to (size minus the length of the problem two lines; don't forget to count any cr/lf characters).

And make a backup of the file before you try this!

1
Juve

Wenn Sie Lösungen im Unix-Stil bevorzugen, können Sie mit Hilfe von drei Codezeilen (Getestet auf Mac und Linux) das Speichern und interaktive Abschneiden von Zeilen durchführen.

kleine + sichere Unix-artige Kürzung (fordert zur Bestätigung auf):

n=2; file=test.csv; tail -n $n $file && read -p "truncate? (y/N)" -n1 key && [ "$key" == "y" ] && perl -e "truncate('$file', `wc -c <$file` - `tail -n $n $file | wc -c` )" 

Diese Lösung basiert auf ein paar gängigen Unix-Tools, wird jedoch immer noch perl -e "truncate(file,length)"als Ersatz verwendet truncate(1), was nicht auf allen Systemen verfügbar ist.

Sie können auch das folgende umfassende wiederverwendbare Shell-Programm verwenden, das Nutzungsinformationen sowie Bestätigungsabschnitte für die Abschneidung, das Analysieren von Optionen und die Fehlerbehandlung enthält.

umfassendes Zeilenkürzungsskript :

#!/usr/bin/env bash  usage(){ cat <<-EOF Usage: $0 [-n NUM] [-h] FILE Options: -n NUM number of lines to remove (default:1) from end of FILE -h show this help EOF exit 1 }  num=1  for opt in $*; do case $opt in -n) num=$2; shift;; -h) usage; break;; *) [ -f "$1" ] && file=$1; shift;; esac done  [ -f "$file" ] || usage  bytes=`wc -c <$file` size=`tail -n $num $file | wc -c`  echo "using perl 'truncate' to remove last $size of $bytes bytes:" tail -n $num $file read -p "truncate these lines? (y/N)" -n1 key && [ "$key" == "y" ] && perl -e "truncate('$file', $bytes - $size )"; echo "" echo "new tail is:"; tail $file 

Hier ist ein Anwendungsbeispiel:

$ cat data/test.csv 1 nice data 2 cool data 3 just data  GARBAGE to be removed (incl. empty lines above and below)  $ ./rmtail.sh -n 3 data/test.csv using perl 'truncate' to remove last 60 of 96 bytes:  GARBAGE to be removed (incl. empty lines above and below)  truncate these lines? (y/N)y new tail is: 1 nice data 2 cool data 3 just data $ cat data/test.csv 1 nice data 2 cool data 3 just data 
0
Justin Smith
#!/bin/sh ed "$1" << HERE $ d d w HERE 

changes are made in place. This is simpler and more efficient than the python script.

Auf meinem System hat "ed" mit einer Textdatei, die aus einer Million Zeilen und über 57 MB besteht, 100 Mal so lange in Anspruch genommen wie mein Python-Skript. Ich kann mir nur vorstellen, wie viel mehr der Unterschied für die 7.000-mal größere OP-Datei sein würde. Dennis Williamson vor 14 Jahren 0