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

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
user31894
источник
Это лучшее решение, поскольку оно простое.
Сяо
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 байтов из конца файла (очевидно, вам нужно точно определить, сколько символов вам нужно удалить).

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

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

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

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

Зак Томпсон
источник
2

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

leeand00
источник
Я верю, что vim загружает только то, что находится непосредственно в буфере при редактировании , однако я не знаю, как это сохранить.
Phoshi
vim зависает при
попытке
Хорошо, если он зависает, ах подождите. Начни загрузку, иди на работу, иди домой, посмотри, сделано ли это.
leeand00
2
Смотрите это: stackoverflow.com/questions/159521/…
leeand00
1

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

Blackbeagle
источник
это отформатированный текст с разделителями в виде строки, однако последние 2 строки по одной колонке, которые нарушат мой импорт, поэтому мне нужно их удалить
Russ Bradberry
исправляет то, что делает "импорт", чтобы иметь дело с этим случаем вариант?
Timday
нет, импорт - это infobright "загрузка данных infile"
Расс Брэдберри
1

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

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

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

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

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

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

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

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

timday
источник
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()
tponthieux
источник
0

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

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

  2. d удалять

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

Стивен Пенни
источник