Эффективно удалить последние две строки очень большого текстового файла

24347
Russ Bradberry

У меня очень большой файл (~ 400 ГБ), и мне нужно удалить из него последние 2 строки. Я пытался использовать sed, но он работал в течение нескольких часов, прежде чем я сдался. Есть ли быстрый способ сделать это, или я застрял sed?

31
Вы можете попробовать GNU head. `head -n -2 file` user31894 14 лет назад 6
В статье http://stackoverflow.com/questions/2580335/matlab-delete-elements-of-binary-files-without-loading-entire-file была приведена пара однострочных предложений по Perl и Java. mtrw 14 лет назад 0

12 ответов на вопрос

31
Dennis Williamson

Я не пробовал это на большом файле, чтобы увидеть, насколько он быстрый, но он должен быть довольно быстрым.

Чтобы использовать сценарий для удаления строк из конца файла:

./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) 
наша система работает под управлением Python 2.4, и я не уверен, что какой-либо из наших сервисов использует его, будет ли это работать? Russ Bradberry 14 лет назад 0
@Russ: Я добавил версию для Python 2.4. Dennis Williamson 14 лет назад 0
абсолютно потрясающе! работал как шарм и менее чем за секунду! Russ Bradberry 14 лет назад 1
12
user31894

Вы можете попробовать голову GNU

head -n -2 file 
Это лучшее решение, поскольку оно простое. xiao 12 лет назад 0
Это покажет ему последние две строки файла, но не удалит их из его файла .. даже не работает в моей системе `head: недопустимое количество строк - -2` SooDesuNe 12 лет назад 1
@SooDesuNe: Нет, он напечатает все строки от начала до 2 строк от конца, как описано в руководстве. Тем не менее, его необходимо перенаправить в файл, и тогда возникает проблема с гигантским размером этого файла, поэтому это не идеальное решение для этой проблемы. Daniel Andersson 12 лет назад 2
+1 Почему это не принимается как правильный ответ? Это быстро, просто и работает как положено. aefxx 12 лет назад 0
@DanielAndersson Почему бы и нет? Вы можете `head -n -2 file> output` ... mreq 11 лет назад 0
@PetrMarek и другие: проблема заключалась в том, что это касалось _giant_ файла. Это решение потребовало бы, чтобы весь файл был передан по каналу и переписал все данные в новое место - и весь вопрос заключается в том, чтобы этого избежать. Требуется решение на месте, например, в принятом ответе. Daniel Andersson 11 лет назад 6
7
timday

Я вижу, что мои системы Debian Squeeze / testing (но не Lenny / stable) включают команду "truncate" как часть пакета "coreutils".

С его помощью вы можете просто сделать что-то вроде

truncate --size=-160 myfile 

удалить 160 байтов из конца файла (очевидно, вам нужно точно определить, сколько символов вам нужно удалить).

Это будет самый быстрый маршрут, поскольку он изменяет файл на месте и поэтому не требует ни копирования, ни анализа файла. Однако вам все равно нужно будет проверить, сколько байтов нужно удалить ... Я думаю, что простой скрипт `dd` сделает это (вам нужно указать смещение ввода, чтобы получить последний килобайт, а затем использовать` tail -2) | LANG = wc -c` или что-то в этом роде). liori 14 лет назад 0
Я использую CentOS, поэтому нет у меня нет усечения. Тем не менее, это именно то, что я ищу. Russ Bradberry 14 лет назад 0
`tail` эффективен и для больших файлов - можно использовать` tail | wc -c` для вычисления количества байтов для обрезки. krlmlr 8 лет назад 0
6
Zac Thompson

Проблема с sed в том, что это потоковый редактор - он будет обрабатывать весь файл, даже если вы хотите вносить изменения ближе к концу. Поэтому, несмотря ни на что, вы создаете новый файл размером 400 ГБ, строка за строкой. Любой редактор, который работает со всем файлом, вероятно, будет иметь эту проблему.

Если вы знаете количество строк, вы можете использовать head, но опять же это создает новый файл вместо того, чтобы изменить существующий на месте. Я полагаю, вы можете получить выигрыш в скорости благодаря простоте действия.

Вы, возможно, лучше удачи, используя splitразбить файл на более мелкие куски, редактирования последнего, а затем с помощью catсоединить их снова, но я не уверен, если это будет лучше. Я бы использовал количество байтов, а не строк, иначе это, вероятно, будет совсем не быстрее - вы все равно будете создавать новый файл объемом 400 ГБ.

2
leeand00

Попробуйте VIM ... Я не уверен, что это сработает или нет, так как я никогда не использовал его на таких больших файлах, но в прошлом я использовал его на более крупных файлах меньшего размера.

Я верю, что vim загружает только то, что находится непосредственно в буфере, при ** редактировании **, однако я не представляю, как это сохранить. Phoshi 14 лет назад 0
vim зависает при попытке загрузить файл Russ Bradberry 14 лет назад 0
Ну, если он зависает, ах подожди. Начни загрузку, иди на работу, иди домой, посмотри, будет ли это сделано. leeand00 14 лет назад 0
Смотрите это: http://stackoverflow.com/questions/159521/text-editor-to-open-big-giant-huge-large-text-files leeand00 14 лет назад 2
1
Blackbeagle

Что за файл и в каком формате? Может быть проще использовать что-то вроде Perl, в зависимости от того, какой это файл - текстовый, графический, двоичный? Как это отформатировано - CSV, TSV ...

это отформатированный текст с разделителями по конвейеру, однако последние 2 строки по одному столбцу, который прервет мой импорт, поэтому мне нужно, чтобы они были удалены Russ Bradberry 14 лет назад 0
исправляет то, что делает "импорт", чтобы иметь дело с этим случаем вариант? timday 14 лет назад 0
нет импорт - это infobright "загрузить данные infile" Russ Bradberry 14 лет назад 0
1
timday

Если вы знаете размер файла в байтах (скажем, 400000000160) и знаете, что вам нужно удалить ровно 160 символов, чтобы убрать последние две строки, тогда что-то вроде

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

должен сделать свое дело. Прошло много лет с тех пор, как я использовал dd в гневе; Кажется, я помню, что дела идут быстрее, если вы используете больший размер блока, но то, сможете ли вы это сделать, зависит от того, достаточно ли кратны строки, которые вы хотите отбросить.

У dd есть несколько других опций для добавления текстовых записей к фиксированному размеру, что может быть полезно в качестве предварительного прохода.

Я попробовал это, но он шел с той же скоростью, что и Сед. За 10 минут было записано около 200 МБ, при такой скорости буквально сотни часов заняли бы. Russ Bradberry 14 лет назад 0
1
timday

Если команда «truncate» не доступна в вашей системе (см. Мой другой ответ), посмотрите на «man 2 truncate» для системного вызова, чтобы обрезать файл до указанной длины.

Очевидно, вам нужно знать, до скольких символов вам нужно обрезать файл (размер минус длина проблемы, две строки; не забудьте подсчитать любые символы cr / lf).

И сделайте резервную копию файла, прежде чем попробовать это!

1
Juve

Если вы предпочитаете решения в стиле Unix, вы можете сохранить и интерактивное усечение строк, используя три строки кода (протестировано на Mac и Linux).

small + safe усечение строки в стиле 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 
0
Justin Smith
#! / Bin / ш  ed "$ 1" << ЗДЕСЬ $ d d вес ВОТ 

изменения сделаны на месте. Это проще и эффективнее, чем скрипт python.

В моей системе, использующей текстовый файл, состоящий из миллиона строк и более 57 МБ, `ed` занимал в 100 раз больше времени, чем мой скрипт на Python. Я могу только представить, насколько больше будет разница для файла ОП, который в 7000 раз больше. Dennis Williamson 14 лет назад 0

Похожие вопросы