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


31

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


6
Вы можете попробовать GNU head. head -n -2 file
user31894

Было несколько однострочных предложений по Perl и Java, приведенных в stackoverflow.com/questions/2580335/…
mtrw

Ответы:


31

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

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

./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: Я добавил версию для Python 2.4.
Приостановлено до дальнейшего уведомления.

1
абсолютно потрясающе! работал как шарм и менее чем за секунду!
Расс Брэдберри

12

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

head -n -2 file

Это лучшее решение, поскольку оно простое.
Сяо

1
Это покажет ему последние две строки файла, но не удалит их из его файла .. даже не работает в моей системеhead: illegal line count -- -2
SooDesuNe

2
@SooDesuNe: Нет, он будет печатать все строки от начала до 2 строк от конца, как описано в руководстве. Тем не менее, это должно быть перенаправлено в файл, а затем возникает проблема с гигантским файлом, так что это не идеальное решение для этой проблемы.
Даниэль Андерссон

+1 Почему это не принимается как правильный ответ? Это быстро, просто и работает как положено.
AEFXX

6
@PetrMarek и другие: проблема заключалась в том, что это касалось гигантского файла. Это решение потребовало бы, чтобы весь файл был передан по каналу и переписал все данные в новое место - и весь вопрос заключается в том, чтобы этого избежать. Требуется решение на месте, например, в принятом ответе.
Даниэль Андерссон

7

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

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

truncate --size=-160 myfile

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


Это будет самый быстрый маршрут, поскольку он изменяет файл на месте и поэтому не требует ни копирования, ни анализа файла. Однако вам все равно нужно будет проверить, сколько байтов нужно удалить ... Я думаю, что простой ddскрипт сделает это (вам нужно указать смещение ввода, чтобы получить последний килобайт, а затем использовать tail -2 | LANG= wc -c, или что-то подобное).
Лиори

Я использую CentOS, поэтому нет у меня нет усечения. Тем не менее, это именно то, что я ищу.
Расс Брэдберри

tailтакже эффективен для больших файлов - можно использовать tail | wc -cдля вычисления количества байт, которые нужно обрезать.
krlmlr

6

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

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

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


2

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


Я верю, что vim загружает только то, что находится непосредственно в буфере при редактировании , однако я не знаю, как это сохранить.
Phoshi

vim зависает при
попытке

Хорошо, если он зависает, ах подождите. Начни загрузку, иди на работу, иди домой, посмотри, сделано ли это.
leeand00

2

1

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


это отформатированный текст с разделителями в виде строки, однако последние 2 строки по одной колонке, которые нарушат мой импорт, поэтому мне нужно их удалить
Russ Bradberry

исправляет то, что делает "импорт", чтобы иметь дело с этим случаем вариант?
Timday

нет, импорт - это infobright "загрузка данных infile"
Расс Брэдберри

1

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

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

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

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


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

1

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

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

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


1

Если вы предпочитаете решения в стиле 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
#! / Bin / ш

ed "$ 1" << ЗДЕСЬ
$
d
d
вес
ВОТ

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


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

0

Изменен принятый ответ для решения аналогичной проблемы. Можно немного подправить, чтобы убрать 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()

0

Вы можете использовать Vim в режиме Ex:

ex -sc '-,d|x' file
  1. -, выберите последние 2 строки

  2. d удалять

  3. x сохранить и закрыть

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.