31

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

12 ответов12

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)
12

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

head -n -2 file
7

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

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

truncate --size=-160 myfile

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

6

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

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

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

2

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

1

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

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
1

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

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

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

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

1

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

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

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

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 сохранить и закрыть

0
#!/bin/sh

ed "$1" << HERE
$
d
d
w
HERE

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

Всё ещё ищете ответ? Посмотрите другие вопросы с метками .