有效地删除一个非常大的文本文件的最后两行


Answers:


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 Bradberry

@Russ:我已经为Python 2.4添加了一个版本。
暂停,直到另行通知。

1
非常精彩!不到一秒钟就像魅力一样运转!
Russ Bradberry 2010年

12

你可以尝试GNU头

head -n -2 file

因为它很简单,所以它是最好的解决方案。
2012年

1
这将向他显示文件的最后两行,但不会将其从文件中删除。.an甚至无法在我的系统上运行head: illegal line count -- -2
SooDesuNe 2012年

2
@SooDesuNe:不,它将按照手册打印从开始到最后两行的所有行。但是,这将需要重定向到一个文件,然后存在该文件过大的问题,因此,这不是解决此问题的完美解决方案。
丹尼尔·安德森

+1为什么不接受此作为正确答案?它快速,简单并且可以按预期工作。
aefxx 2012年

6
@PetrMarek和其他人:问题在于它涉及一个巨大的文件。这种解决方案将需要通过管道来馈送整个文件,并将所有数据重写到一个新位置-问题的全部重点是避免这种情况。需要就地解决方案,例如已接受答案中的解决方案。
Daniel Andersson

7

我看到我的Debian Squeeze /测试系统(但Lenny / stable没有)包括“ truncate”命令作为“ coreutils”软件包的一部分。

有了它,你可以简单地做

truncate --size=-160 myfile

从文件末尾删除160个字节(显然,您需要确切地确定需要删除多少个字符)。


这将是最快的路线,因为它可以就地修改文件,因此不需要复制或解析文件。但是,您仍然需要检查要删除多少个字节... / guesss /一个简单的dd脚本可以做到这一点(您需要指定输入偏移量以获取fe的最后一个千字节,然后使用tail -2 | LANG= wc -c或sth之类的东西)。
liori 2010年

我正在使用CentOS,所以不,我没有截断。但是,这正是我想要的。
Russ Bradberry

tail对于大型文件也非常有效-可以用于tail | wc -c计算要修剪的字节数。
krlmlr

6

sed的问题在于它是一个流编辑器-即使您只想在末尾进行修改,它也会处理整个文件。因此,无论如何,您都在逐行创建一个新的400GB文件。任何对整个文件进行操作的编辑器都可能会出现此问题。

如果知道行数,则可以使用head,但这又会创建一个新文件,而不是更改现有文件。我想,您可以从操作的简单性中获得速度上的提高。

可能会比较幸运,可以使用split将文件分成较小的部分,编辑最后一个部分,然后cat再次使用它们进行组合,但是我不确定它是否会更好。我将使用字节数而不是行数,否则它可能根本不会更快-您仍将创建一个新的400GB文件。


2

尝试使用VIM ...我不确定它是否会成功,因为我从未在如此大的文件上使用过它,但过去我曾在较小的较大文件上使用过它,请尝试一下。


我确实相信vim仅在编辑时加载缓冲区周围的内容,但是我不知道它如何保存。
Phoshi 2010年

vim在尝试加载文件时挂起
Russ Bradberry 10-4-5

好吧,如果挂了,啊,等一下。开始加载,开始工作,回家,看看是否完成。
leeand00'4


1

什么样的文件和什么格式?使用Perl之类的东西可能更容易,这取决于它是哪种文件-文本,图形,二进制文件?格式如何-CSV,TSV ...


它是格式化的管道删除文本,但是最后两行是一列,每列都会破坏我的导入,因此我需要将其删除
Russ Bradberry 2010年

正在修复“导入”来处理这种情况的任何选择吗?
2010年

没有导入是infobright的“加载数据文件”
Russ Bradberry

1

如果您知道文件的大小为字节(例如400000000160),并且您知道需要准确删除160个字符以去除最后两行,那么类似

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

应该可以。自从我用dd发怒以来已经有好几年了。我似乎记得,如果使用更大的块大小,事情进展得更快,但是是否能够做到这一点取决于您要删除的行是否是理想的倍数。

dd还有一些其他选项可以将文本记录填充到固定大小,这可能对初步通过很有用。


我试过了,但是速度和sed差不多。它在10分钟内写了大约200MB,以这种速度,它实际上需要数百小时才能完成。
Russ Bradberry

1

如果您的系统上没有“ truncate”命令(请参阅我的其他答案),请查看“ man 2 truncate”以进行系统调用以将文件截断为指定的长度。

显然,您需要知道将文件截断到多少个字符(大小减去问题的长度两行;不要忘记计算任何cr / lf字符)。

尝试之前,请备份文件!


1

如果您喜欢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),并非在所有系统上都可用。

您还可以使用以下全面的可重复使用的Shell程序,该程序提供使用信息并具有截断确认,选项分析和错误处理的功能。

全面的行截断脚本

#!/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 / sh

ed“ $ 1” <<这里
$
d
d
w
这里

更改已就位。这比python脚本更简单,更高效。


在我的系统上,使用包含一百万行和超过57MB的文本文件ed,执行时间是Python脚本的100倍。我只能想象OP的文件大小相差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

您可以在Ex模式下使用Vim:

ex -sc '-,d|x' file
  1. -, 选择最后两行

  2. d 删除

  3. x 保存并关闭

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.