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
?
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
?
Câu trả lời:
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)
bạn có thể thử đầu GNU
head -n -2 file
head: illegal line count -- -2
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).
dd
tậ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ế).
tail
cũ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.
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.
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.
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 ...
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ộ.
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!
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
#! / 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.
ed
mấ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.
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()
Bạn có thể sử dụng Vim trong chế độ Ex:
ex -sc '-,d|x' file
-,
chọn 2 dòng cuối
d
xóa bỏ
x
lưu và đóng
head -n -2 file