У мене дуже великий файл (~ 400 ГБ), і мені потрібно видалити з нього останні 2 рядки. Я намагався використовувати sed
, але це пробігло години, перш ніж я здався. Чи є швидкий спосіб зробити це, або я застряг sed
?
У мене дуже великий файл (~ 400 ГБ), і мені потрібно видалити з нього останні 2 рядки. Я намагався використовувати sed
, але це пробігло години, перш ніж я здався. Чи є швидкий спосіб зробити це, або я застряг sed
?
Відповіді:
Я не пробував цього на великому файлі, щоб побачити, наскільки це швидко, але це має бути досить швидко.
Щоб використовувати скрипт для видалення рядків з кінця файлу:
./shorten.py 2 large_file.txt
Він прагне до кінця файлу, перевіряє, чи є останній символ новим рядком, а потім зчитує кожен символ по черзі, повертаючись назад, поки не знайдеться три нові рядки і обрізає файл відразу після цього пункту. Зміна робиться на місці.
Редагувати: я додав версію Python 2.4 внизу.
Ось версія для 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)
Ось версія 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)
Ось версія 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)
ви можете спробувати голову GNU
head -n -2 file
head: illegal line count -- -2
Я бачу, що мої системи Debian Squeeze / тестування (але не Lenny / stable) включають команду "усікати" як частину пакету "coreutils".
З ним можна просто зробити щось на кшталт
truncate --size=-160 myfile
щоб видалити 160 байт з кінця файлу (очевидно, потрібно точно розібратися, скільки символів потрібно видалити).
dd
скрипт зробить це (вам потрібно вказати зміщення входу, щоб отримати останній кілобайт, а потім використовувати tail -2 | LANG= wc -c
, або що-небудь подібне).
tail
ефективний і для великих файлів - може використовувати tail | wc -c
для обчислення кількості байтів, які слід обрізати.
Проблема sed полягає в тому, що це редактор потоків - він обробить весь файл, навіть якщо ви хочете лише внести зміни до кінця. Тож незалежно від того, ви створюєте новий 400 Гб файл, рядок за рядком. Будь-який редактор, який працює над усім файлом, мабуть, матиме цю проблему.
Якщо ви знаєте кількість рядків, ви можете використовувати head
, але знову ж таки це створює новий файл замість того, щоб змінювати існуючий на місці. Можливо, ви можете отримати швидкість завдяки простоті дії, я думаю.
Ви , можливо , краще удачі , використовуючи split
розбити файл на більш дрібні шматки, редагування останнього, а потім з допомогою cat
з'єднати їх знову, але я не впевнений , якщо це буде краще. Я б використовував кількість байтів, а не рядки, інакше це, швидше за все, не буде швидше - ви все одно будете створювати новий 400 ГБ файл.
Спробуйте VIM ... Я не впевнений, чи вдасться це зробити, чи ні, так як я ніколи не використовував його на такому великому файлі, але я раніше його використовував на менших великих файлах, дайте спробувати.
Який файл і в якому форматі? Можливо, буде простіше використовувати щось на кшталт Perl, залежне від типу файлу - текст, графіка, двійковий файл? Як це відформатовано - CSV, TSV ...
Якщо ви знаєте розмір файлу до байта (400000000160 кажуть) і знаєте, що вам потрібно видалити рівно 160 символів, щоб зняти останні два рядки, то щось подібне
dd if=originalfile of=truncatedfile ibs=1 count=400000000000
повинен зробити трюк. Минуло віки, коли я в гніві використовував дд; Здається, я пам’ятаю, що справи йдуть швидше, якщо ви використовуєте більший розмір блоку, але від того, чи зможете ви це зробити, залежить від того, чи будуть лінії, які ви хочете відпустити, у кращому кратному.
У dd є деякі інші варіанти, щоб викласти текстові записи до фіксованого розміру, які можуть бути корисними як попередній пропуск.
Якщо команда "усікати" недоступна у вашій системі (див. Мою іншу відповідь), перегляньте "усікання чоловіка 2" для системного виклику для врізання файлу заданої довжини.
Очевидно, ви повинні знати, скільки символів потрібно для врізання файлу (розмір мінус довжина проблеми два рядки; не забудьте порахувати жодні символи cr / lf).
І зробіть резервну копію файлу, перш ніж спробувати це!
Якщо ви віддаєте перевагу рішенням у стилі unix, ви можете мати збереження та інтерактивне обрізання ліній, використовуючи три рядки коду (випробувано на Mac та Linux).
невеликий + безпечний відсікання ліній у стилі Unix (запитує підтвердження):
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` )"
Це рішення покладається на кілька поширених інструментів unix, але все ще використовується perl -e "truncate(file,length)"
як найближча заміна truncate(1)
, яка доступна не у всіх системах.
Ви також можете використовувати наступну комплексну програму оболонки для багаторазового використання, яка надає інформацію про використання та функції підтвердження усікання, розбору параметрів та обробки помилок.
всебічний сценарій усікання рядків :
#!/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
Ось приклад використання:
$ 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
#! / бін / ш ed "$ 1" << ТУТ $ г г ш ТУТ
зміни вносяться на місце. Це простіше і ефективніше, ніж сценарій python.
ed
потрібно було виконати в 100 разів довше, ніж мій сценарій Python. Я можу лише уявити, наскільки більша різниця була б у файлі ОП, який у 7000 разів більший.
Змінено прийняту відповідь, щоб вирішити подібну проблему. Не вдалося трохи підправити, щоб видалити n рядків.
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
І відповідний тест:
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()
Ви можете використовувати Vim в режимі Ex:
ex -sc '-,d|x' file
-,
виберіть останні 2 рядки
d
видалити
x
зберегти і закрити
head -n -2 file