Mam bardzo duży plik (~ 400 GB) i muszę z niego usunąć ostatnie 2 linie. Próbowałem użyć sed
, ale działało to przez wiele godzin, zanim się poddałem. Czy istnieje na to szybki sposób lub utknąłem sed
?
Mam bardzo duży plik (~ 400 GB) i muszę z niego usunąć ostatnie 2 linie. Próbowałem użyć sed
, ale działało to przez wiele godzin, zanim się poddałem. Czy istnieje na to szybki sposób lub utknąłem sed
?
Odpowiedzi:
Nie próbowałem tego na dużym pliku, aby zobaczyć, jak to jest szybkie, ale powinno być dość szybkie.
Aby użyć skryptu do usunięcia wierszy z końca pliku:
./shorten.py 2 large_file.txt
Szuka do końca pliku, sprawdza, czy ostatni znak jest nową linią, a następnie odczytuje każdy znak po kolei, cofając się, aż znajdzie trzy nowe linie i obcina plik zaraz po tym punkcie. Zmiana została wprowadzona.
Edycja: Dodałem wersję Python 2.4 na dole.
Oto wersja dla 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)
Oto wersja Python 3:
#!/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)
Oto wersja Python 2.4:
#!/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)
możesz spróbować GNU head
head -n -2 file
head: illegal line count -- -2
Widzę, że moje systemy wyciskania / testowania Debiana (ale nie Lenny / stable) zawierają polecenie „obcinania” jako część pakietu „coreutils”.
Dzięki niemu możesz po prostu zrobić coś takiego
truncate --size=-160 myfile
aby usunąć 160 bajtów z końca pliku (oczywiście musisz dokładnie ustalić, ile znaków musisz usunąć).
dd
zrobi to prosty skrypt (musisz określić przesunięcie wejściowe, aby uzyskać np. Ostatni kilobajt, a następnie użyć tail -2 | LANG= wc -c
, lub coś takiego).
tail
jest również skuteczny w przypadku dużych plików - można go użyć tail | wc -c
do obliczenia liczby bajtów do przycięcia.
Problem z sed polega na tym, że jest to edytor strumieniowy - przetworzy cały plik, nawet jeśli chcesz wprowadzać modyfikacje tylko pod koniec. Niezależnie od tego, tworzysz nowy plik 400 GB, linia po linii. Każdy edytor działający na całym pliku prawdopodobnie będzie miał ten problem.
Jeśli znasz liczbę wierszy, możesz użyć head
, ale ponownie tworzy to nowy plik zamiast zmieniać istniejący. Wydaje mi się, że możesz uzyskać wzrost prędkości dzięki prostocie akcji.
Państwo może mieć więcej szczęścia przy użyciu split
złamać plik na mniejsze kawałki, edytując ostatni, a następnie za pomocą cat
połączyć je ponownie, ale nie jestem pewien, czy to będzie lepiej. Użyłbym raczej liczby bajtów niż linii, w przeciwnym razie prawdopodobnie nie będzie to wcale szybsze - nadal będziesz tworzyć nowy plik 400 GB.
Wypróbuj VIM ... Nie jestem pewien, czy to zrobi, czy nie, ponieważ nigdy nie użyłem go na tak dużym pliku, ale użyłem go na mniejszych większych plikach w przeszłości, spróbuj.
Jakiego rodzaju plik iw jakim formacie? Może być łatwiejsze w użyciu coś takiego jak Perl w zależności od rodzaju pliku - tekst, grafika, plik binarny? Jak jest sformatowany - CSV, TSV ...
Jeśli znasz rozmiar pliku do bajtu (powiedzmy 400000000160) i wiesz, że musisz usunąć dokładnie 160 znaków, aby usunąć dwa ostatnie wiersze, to coś w rodzaju
dd if=originalfile of=truncatedfile ibs=1 count=400000000000
powinien załatwić sprawę. Minęły wieki, odkąd użyłem dd w gniewie; Wydaje mi się, że pamiętam, że rzeczy idą szybciej, jeśli używasz większego rozmiaru bloku, ale to, czy możesz to zrobić, zależy od tego, czy linie, które chcesz upuścić, są w dobrym wielokrotności.
dd ma kilka innych opcji wypisywania rekordów tekstowych do ustalonego rozmiaru, które mogą być przydatne jako wstępne przejście.
Jeśli polecenie „obcinania” nie jest dostępne w twoim systemie (patrz moja inna odpowiedź), spójrz na „obcinanie man 2” dla wywołania systemowego w celu obcinania pliku do określonej długości.
Oczywiście musisz wiedzieć, do ilu znaków musisz obciąć plik (rozmiar minus długość problemu dwie linie; nie zapomnij policzyć znaków cr / lf).
I wykonaj kopię zapasową pliku, zanim spróbujesz!
Jeśli wolisz rozwiązania w stylu uniksowym, możesz zapisać i interaktywne obcinanie linii za pomocą trzech linii kodu (testowane na komputerach Mac i Linux).
małe + bezpieczne obcinanie linii w stylu uniksowym (prosi o potwierdzenie):
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` )"
To rozwiązanie opiera się na kilku popularnych narzędziach uniksowych, ale nadal stosuje się je perl -e "truncate(file,length)"
jako najbliższą zamiennik dla truncate(1)
, który nie jest dostępny we wszystkich systemach.
Możesz także skorzystać z następującego kompleksowego programu powłoki wielokrotnego użytku, który zawiera informacje o użytkowaniu oraz funkcje potwierdzania obcięcia, analizy opcji i obsługi błędów.
kompleksowy skrypt obcięcia linii :
#!/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
Oto przykład użycia:
$ 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” << TUTAJ $ re re w TUTAJ
zmiany są wprowadzane. Jest to prostsze i bardziej wydajne niż skrypt Pythona.
ed
zajęło 100 razy więcej czasu niż wykonanie skryptu w języku Python. Mogę sobie tylko wyobrazić, o ile większa byłaby różnica dla pliku OP, który jest 7000 razy większy.
Zmodyfikowano zaakceptowaną odpowiedź, aby rozwiązać podobny problem. Można go trochę ulepszyć, aby usunąć n linii.
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
I odpowiedni 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()
Możesz używać Vima w trybie Ex:
ex -sc '-,d|x' file
-,
wybierz ostatnie 2 linie
d
kasować
x
Zapisz i zamknij
head -n -2 file