Ich habe eine sehr große Datei (~ 400 GB) und muss die letzten 2 Zeilen entfernen. Ich habe versucht, zu verwenden sed
, aber es lief stundenlang, bevor ich aufgab. Gibt es eine schnelle Möglichkeit, dies zu tun, oder bleibe ich dabei sed
?
Ich habe eine sehr große Datei (~ 400 GB) und muss die letzten 2 Zeilen entfernen. Ich habe versucht, zu verwenden sed
, aber es lief stundenlang, bevor ich aufgab. Gibt es eine schnelle Möglichkeit, dies zu tun, oder bleibe ich dabei sed
?
Antworten:
Ich habe dies bei einer großen Datei nicht versucht, um zu sehen, wie schnell es ist, aber es sollte ziemlich schnell sein.
So entfernen Sie mit dem Skript Zeilen am Ende einer Datei:
./shorten.py 2 large_file.txt
Es sucht bis zum Ende der Datei, prüft, ob das letzte Zeichen eine neue Zeile ist, liest dann jedes Zeichen einzeln rückwärts, bis drei neue Zeilen gefunden wurden, und schneidet die Datei unmittelbar nach diesem Punkt ab. Die Änderung wurde vorgenommen.
Bearbeiten: Ich habe eine Python 2.4-Version am unteren Rand 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)
Sie können GNU Kopf versuchen
head -n -2 file
head: illegal line count -- -2
Ich sehe, dass meine Debian Squeeze / Testing-Systeme (aber nicht Lenny / stable) einen "Truncate" -Befehl als Teil des "Coreutils" -Pakets enthalten.
Damit könnte man einfach so etwas machen
truncate --size=-160 myfile
um 160 Bytes vom Ende der Datei zu entfernen (offensichtlich müssen Sie genau herausfinden, wie viele Zeichen Sie entfernen müssen).
dd
Skript dies tun wird (Sie müssen den Eingabeversatz angeben, um zB das letzte Kilobyte zu erhalten und dann zu verwenden tail -2 | LANG= wc -c
, oder so etwas).
tail
ist auch für große Dateien effizient - kann verwendet werden tail | wc -c
, um die Anzahl der zu schneidenden Bytes zu berechnen.
Das Problem bei sed ist, dass es sich um einen Stream-Editor handelt - er verarbeitet die gesamte Datei, auch wenn Sie erst gegen Ende Änderungen vornehmen möchten. Auf jeden Fall erstellen Sie zeilenweise eine neue 400-GB-Datei. Jeder Editor, der die gesamte Datei bearbeitet, wird wahrscheinlich dieses Problem haben.
Wenn Sie die Anzahl der Zeilen kennen, können Sie head
diese verwenden. Dadurch wird jedoch eine neue Datei erstellt, anstatt die vorhandene zu ändern. Sie könnten Geschwindigkeitsgewinne durch die Einfachheit der Aktion erzielen, denke ich.
Sie könnte mehr Glück mit split
der Datei in kleinere Stücke zu brechen, die letzte Bearbeitung, und dann mit cat
wieder , sie zu kombinieren, aber ich bin nicht sicher , ob es nicht besser sein. Ich würde eher die Anzahl der Bytes als die Anzahl der Zeilen verwenden, sonst wird es wahrscheinlich gar nicht schneller - Sie werden immer noch eine neue 400-GB-Datei erstellen.
Versuchen Sie es mit VIM ... Ich bin mir nicht sicher, ob es funktioniert oder nicht, da ich es noch nie für eine so große Datei verwendet habe, aber ich habe es in der Vergangenheit für kleinere, größere Dateien verwendet, probieren Sie es aus.
Welche Art von Datei und in welchem Format? Kann es einfacher sein, etwas wie Perl zu verwenden, abhängig davon, um welche Art von Datei es sich handelt - Text, Grafiken, Binärdateien? Wie ist es formatiert - CSV, TSV ...
Wenn Sie die Größe der Datei auf das Byte (400000000160 sagen) kennen und wissen, dass Sie genau 160 Zeichen entfernen müssen, um die letzten beiden Zeilen zu entfernen, dann ist so etwas wie
dd if=originalfile of=truncatedfile ibs=1 count=400000000000
sollte den Trick machen. Es ist schon eine Ewigkeit her, dass ich dd im Zorn benutzt habe. Ich erinnere mich, dass die Dinge schneller gehen, wenn Sie einen größeren Block verwenden, aber ob Sie dies tun können, hängt davon ab, ob die Zeilen, die Sie löschen möchten, ein nettes Vielfaches haben.
dd verfügt über einige andere Optionen zum Auffüllen von Textdatensätzen mit einer festen Größe, die als vorläufiger Durchgang nützlich sein kann.
Wenn der Befehl "Truncate" auf Ihrem System nicht verfügbar ist (siehe meine andere Antwort), sehen Sie sich "Man 2 Truncate" für den Systemaufruf an, um eine Datei auf eine bestimmte Länge zu kürzen.
Natürlich müssen Sie wissen, auf wie viele Zeichen Sie die Datei kürzen müssen (Größe abzüglich der Länge der zwei Zeilen des Problems; vergessen Sie nicht, alle cr / lf-Zeichen zu zählen).
Erstellen Sie eine Sicherungskopie der Datei, bevor Sie dies versuchen!
Wenn Sie Lösungen im Unix-Stil bevorzugen, können Sie die Zeilen mithilfe von drei Codezeilen speichern und interaktiv abschneiden (Getestet auf Mac und Linux).
Small + Safe-Unix-Zeilenabbruch (Bestätigung erforderlich):
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, verwendet aber immer noch den perl -e "truncate(file,length)"
nächstliegenden Ersatz für truncate(1)
, der nicht auf allen Systemen verfügbar ist.
Sie können auch das folgende umfassende Programm für wiederverwendbare Shells verwenden, das Informationen zur Verwendung enthält und eine Bestätigung der Kürzung, Analyse von Optionen und Fehlerbehandlung bietet.
Umfassendes Skript zum Abschneiden von Zeilen :
#!/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
#! / bin / sh ed "$ 1" << HIER $ d d w HIER
Änderungen werden vorgenommen. Dies ist einfacher und effizienter als das Python-Skript.
ed
dauerte die Ausführung einer aus einer Million Zeilen und über 57 MB bestehenden Textdatei 100-mal so lange wie bei meinem Python-Skript. Ich kann mir nur vorstellen, wie viel mehr der Unterschied für die 7000-fach größere OP-Datei wäre.
Die akzeptierte Antwort wurde geändert, um ein ähnliches Problem zu lösen. Könnte ein wenig optimiert werden, um n Zeilen zu entfernen.
import os
def clean_up_last_line(file_path):
"""
cleanup last incomplete line from a file
helps with an unclean shutdown of a program that appends to a file
if \n is not the last character, remove the line
"""
with open(file_path, 'r+b') as f:
f.seek(0, os.SEEK_END)
while f.tell() > 0: ## current position is greater than zero
f.seek(-1, os.SEEK_CUR)
if f.read(1) == '\n':
f.truncate()
break
f.seek(-1, os.SEEK_CUR) ## don't quite understand why this has to be called again, but it doesn't work without it
Und der entsprechende Test:
import unittest
class CommonUtilsTest(unittest.TestCase):
def test_clean_up_last_line(self):
"""
remove the last incomplete line from a huge file
a line is incomplete if it does not end with a line feed
"""
file_path = '/tmp/test_remove_last_line.txt'
def compare_output(file_path, file_data, expected_output):
"""
run the same test on each input output pair
"""
with open(file_path, 'w') as f:
f.write(file_data)
utils.clean_up_last_line(file_path)
with open(file_path, 'r') as f:
file_data = f.read()
self.assertTrue(file_data == expected_output, file_data)
## test a multiline file
file_data = """1362358424445914,2013-03-03 16:53:44,34.5,151.16345879,b
1362358458954466,2013-03-03 16:54:18,34.5,3.0,b
1362358630923094,2013-03-03 16:57:10,34.5,50.0,b
136235"""
expected_output = """1362358424445914,2013-03-03 16:53:44,34.5,151.16345879,b
1362358458954466,2013-03-03 16:54:18,34.5,3.0,b
1362358630923094,2013-03-03 16:57:10,34.5,50.0,b
"""
compare_output(file_path, file_data, expected_output)
## test a file with no line break
file_data = u"""1362358424445914,2013-03-03 16:53:44,34.5,151.16345879,b"""
expected_output = "1362358424445914,2013-03-03 16:53:44,34.5,151.16345879,b"
compare_output(file_path, file_data, expected_output)
## test a file a leading line break
file_data = u"""\n1362358424445914,2013-03-03 16:53:44,34.5,151.16345879,b"""
expected_output = "\n"
compare_output(file_path, file_data, expected_output)
## test a file with one line break
file_data = u"""1362358424445914,2013-03-03 16:53:44,34.5,151.16345879,b\n"""
expected_output = """1362358424445914,2013-03-03 16:53:44,34.5,151.16345879,b\n"""
compare_output(file_path, file_data, expected_output)
os.remove(file_path)
if __name__ == '__main__':
unittest.main()
Sie können Vim im Ex-Modus verwenden:
ex -sc '-,d|x' file
-,
wähle die letzten 2 Zeilen aus
d
löschen
x
speichern und schließen
head -n -2 file