Loại bỏ hiệu quả hai dòng cuối cùng của một tệp văn bản cực lớn


31

Tôi có một tệp rất lớn (~ 400 GB) và tôi cần xóa 2 dòng cuối cùng khỏi nó. Tôi đã cố gắng sử dụng sed, nhưng nó đã chạy trong nhiều giờ trước khi tôi từ bỏ. Có một cách nhanh chóng để làm điều này, hoặc tôi bị mắc kẹt sed?


6
bạn có thể thử GNU. head -n -2 file
dùng31894

Có một vài gợi ý về Perl và Java được đưa ra trong stackoverflow.com/questions/2580335/
Đổi

Câu trả lời:


31

Tôi đã không thử điều này trên một tệp lớn để xem nó nhanh như thế nào, nhưng nó sẽ khá nhanh.

Để sử dụng tập lệnh để xóa các dòng từ cuối tệp:

./shorten.py 2 large_file.txt

Nó tìm đến cuối tập tin, kiểm tra để chắc chắn rằng ký tự cuối cùng là một dòng mới, sau đó đọc từng ký tự một lần đi ngược lại cho đến khi tìm thấy ba dòng mới và cắt ngắn tệp ngay sau điểm đó. Sự thay đổi được thực hiện tại chỗ.

Chỉnh sửa: Tôi đã thêm một phiên bản Python 2.4 ở phía dưới.

Đây là phiên bản dành cho 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)

Đây là phiên bản 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)

Đây là phiên bản 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)

hệ thống của chúng tôi đang chạy python 2.4 và tôi không chắc liệu có dịch vụ nào của chúng tôi dựa vào nó không, liệu nó có hoạt động trong đó không?
Russ Bradberry

@Russ: Tôi đã thêm một phiên bản cho Python 2.4.
Tạm dừng cho đến khi có thông báo mới.

1
hoàn toàn tuyệt vời! làm việc như một lá bùa và trong chưa đầy một giây!
Russ Bradberry

12

bạn có thể thử đầu GNU

head -n -2 file

Đó là giải pháp tốt nhất vì nó đơn giản.
xiao

1
Điều này sẽ cho anh ta thấy hai dòng cuối cùng của tệp, nhưng không xóa chúng khỏi tệp của anh ta..tôi thậm chí không hoạt động trên hệ thống của tôihead: illegal line count -- -2
SooDesuNe

2
@SooDesuNe: Không, nó sẽ in tất cả các dòng từ đầu đến 2 dòng từ cuối, theo hướng dẫn. Tuy nhiên, điều này sẽ cần phải được chuyển hướng đến một tệp, và sau đó có vấn đề với tệp này là khổng lồ, vì vậy nó không phải là giải pháp hoàn hảo cho vấn đề này.
Daniel Andersson

+1 Tại sao điều này không được chấp nhận là câu trả lời đúng? Nó nhanh, đơn giản và hoạt động như mong đợi.
aefxx

6
@PetrMarek và những người khác: Vấn đề là nó liên quan đến một tập tin khổng lồ . Giải pháp này sẽ yêu cầu toàn bộ tệp được đưa qua một đường ống và viết lại tất cả dữ liệu đến một vị trí mới - và toàn bộ vấn đề của câu hỏi là để tránh điều đó. Một giải pháp tại chỗ là cần thiết, chẳng hạn như giải pháp được chấp nhận.
Daniel Andersson

7

Tôi thấy các hệ thống kiểm tra / nén Debian của tôi (nhưng không phải Lenny / ổn định) bao gồm lệnh "cắt ngắn" như là một phần của gói "coreutils".

Với nó, bạn có thể chỉ cần làm một cái gì đó như

truncate --size=-160 myfile

để xóa 160 byte khỏi phần cuối của tệp (rõ ràng bạn cần phải tìm ra chính xác có bao nhiêu ký tự bạn cần xóa).


Đây sẽ là con đường nhanh nhất vì nó sửa đổi tệp tại chỗ, và do đó không yêu cầu sao chép cũng không phân tích tệp. Tuy nhiên, bạn vẫn sẽ cần kiểm tra có bao nhiêu byte để loại bỏ ... Tôi / đoán / rằng một ddtập lệnh đơn giản sẽ làm điều đó (bạn cần chỉ định bù đầu vào để lấy kilobyte cuối cùng và sau đó sử dụng tail -2 | LANG= wc -c, hoặc sth như thế).
liori

Tôi đang sử dụng CentOS, vì vậy không có tôi không bị cắt cụt. Tuy nhiên, đây chính xác là những gì tôi đang tìm kiếm.
Russ Bradberry

tailcũng hiệu quả đối với các tệp lớn - có thể sử dụng tail | wc -cđể tính toán số byte cần cắt.
krlmlr

6

Vấn đề với sed là nó là một trình chỉnh sửa luồng - nó sẽ xử lý toàn bộ tệp ngay cả khi bạn chỉ muốn thực hiện sửa đổi gần cuối. Vì vậy, không có vấn đề gì, bạn đang tạo một tệp 400 GB mới, từng dòng một. Bất kỳ trình soạn thảo nào hoạt động trên toàn bộ tệp có thể sẽ có vấn đề này.

Nếu bạn biết số lượng dòng, bạn có thể sử dụng head, nhưng một lần nữa điều này sẽ tạo ra một tệp mới thay vì thay đổi vị trí hiện có. Bạn có thể nhận được tốc độ tăng từ sự đơn giản của hành động, tôi đoán.

Bạn có thể may mắn hơn khi sử dụng splitđể chia tệp thành các phần nhỏ hơn, chỉnh sửa tệp cuối cùng và sau đó sử dụng catđể kết hợp chúng lại, nhưng tôi không chắc liệu nó có tốt hơn không. Tôi sẽ sử dụng số byte thay vì các dòng, nếu không, nó có thể sẽ không nhanh hơn chút nào - bạn vẫn sẽ tạo một tệp 400GB mới.


2

Hãy thử VIM ... Tôi không chắc liệu nó có thực hiện được mẹo hay không, vì tôi chưa bao giờ sử dụng nó trên một tệp lớn như vậy, nhưng trước đây tôi đã sử dụng nó trên các tệp lớn hơn nhỏ hơn.


Tôi tin rằng vim chỉ tải những gì ngay lập tức xung quanh bộ đệm khi chỉnh sửa , tuy nhiên tôi không biết nó tiết kiệm như thế nào.
Phoshi

vim bị treo trong khi nó cố tải tập tin
Russ Bradberry

Vâng, nếu nó bị treo, ah chờ nó. Bắt đầu tải, đi làm, về nhà, xem đã xong chưa.
leeand00


1

Những loại tập tin và trong định dạng? Có thể dễ dàng hơn để sử dụng một cái gì đó như Perl phụ thuộc vào loại tệp - văn bản, đồ họa, nhị phân? Nó được định dạng như thế nào - CSV, TSV ...


đó là văn bản được định dạng đường ống được định dạng, tuy nhiên 2 dòng cuối cùng là một cột sẽ phá vỡ quá trình nhập của tôi vì vậy tôi cần xóa chúng
Russ Bradberry

đang sửa chữa bất cứ điều gì "nhập khẩu" để đối phó với trường hợp này là một tùy chọn?
timday

không có bản nhập nào là "infile data infile" của
infobright

1

Nếu bạn biết kích thước của tệp theo byte (400000000160 nói) và bạn biết rằng bạn cần xóa chính xác 160 ký tự để loại bỏ hai dòng cuối cùng, thì đại loại như

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

nên làm thủ thuật. Đã lâu rồi tôi mới dùng dd trong cơn giận; Tôi dường như nhớ mọi thứ diễn ra nhanh hơn nếu bạn sử dụng kích thước khối lớn hơn, nhưng liệu bạn có thể làm điều đó hay không phụ thuộc vào việc các dòng bạn muốn thả có ở mức nhiều hay không.

dd có một số tùy chọn khác để đệm các bản ghi văn bản đến một kích thước cố định có thể hữu ích như một bước sơ bộ.


Tôi đã thử nó, nhưng nó có cùng tốc độ với sed. Nó đã viết khoảng 200 MB trong 10 phút, với tốc độ này, nó sẽ mất hàng trăm giờ để hoàn thành.
Russ Bradberry

1

Nếu lệnh "cắt ngắn" không khả dụng trên hệ thống của bạn (xem câu trả lời khác của tôi), hãy xem "man 2 truncate" cho lệnh gọi hệ thống để cắt một tệp theo độ dài đã chỉ định.

Rõ ràng bạn cần biết bạn cần cắt bao nhiêu ký tự (kích thước trừ đi độ dài của vấn đề hai dòng; đừng quên đếm bất kỳ ký tự cr / lf nào).

Và tạo một bản sao lưu của tập tin trước khi bạn thử điều này!


1

Nếu bạn thích các giải pháp theo kiểu unix, bạn có thể lưu và cắt ngắn dòng tương tác bằng ba dòng mã (Đã thử nghiệm trên Mac và Linux).

cắt ngắn dòng nhỏ + an toàn theo kiểu (yêu cầu xác nhận):

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` )"

Giải pháp này dựa trên một vài công cụ unix phổ biến, nhưng vẫn sử dụng perl -e "truncate(file,length)"như là sự thay thế gần nhất cho truncate(1), không có sẵn trên tất cả các hệ thống.

Bạn cũng có thể sử dụng chương trình shell có thể tái sử dụng toàn diện sau đây, cung cấp thông tin sử dụng và tính năng xác nhận cắt ngắn, phân tích tùy chọn và xử lý lỗi.

kịch bản cắt ngắn dòng toàn diện :

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

Dưới đây là một ví dụ sử dụng:

$ 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
#! / thùng / sh

ed "$ 1" << TẠI ĐÂY
$
d
d
w
ĐÂY

thay đổi được thực hiện tại chỗ. Điều này đơn giản và hiệu quả hơn so với kịch bản python.


Trên hệ thống của tôi, sử dụng tệp văn bản bao gồm một triệu dòng và trên 57 MB, edmất 100 lần thời gian để thực thi so với tập lệnh Python của tôi. Tôi chỉ có thể tưởng tượng sự khác biệt sẽ lớn hơn bao nhiêu đối với tệp OP lớn hơn 7000 lần.
Tạm dừng cho đến khi có thông báo mới.

0

Sửa đổi câu trả lời được chấp nhận để giải quyết một vấn đề tương tự. Có thể được điều chỉnh một chút để loại bỏ n dòng.

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

Và bài kiểm tra tương ứng:

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

Bạn có thể sử dụng Vim trong chế độ Ex:

ex -sc '-,d|x' file
  1. -, chọn 2 dòng cuối

  2. d xóa bỏ

  3. x lưu và đóng

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.