ฉันมีไฟล์ที่มีขนาดใหญ่มาก (~ 400 GB) และฉันต้องลบ 2 บรรทัดสุดท้ายออกจากไฟล์ ฉันพยายามใช้sed
แต่มันวิ่งไปหลายชั่วโมงก่อนจะยอมแพ้ มีวิธีที่รวดเร็วในการทำเช่นนี้หรือฉันติดอยู่กับsed
?
ฉันมีไฟล์ที่มีขนาดใหญ่มาก (~ 400 GB) และฉันต้องลบ 2 บรรทัดสุดท้ายออกจากไฟล์ ฉันพยายามใช้sed
แต่มันวิ่งไปหลายชั่วโมงก่อนจะยอมแพ้ มีวิธีที่รวดเร็วในการทำเช่นนี้หรือฉันติดอยู่กับsed
?
คำตอบ:
ฉันไม่ได้ลองในไฟล์ขนาดใหญ่เพื่อดูว่ามันเร็วแค่ไหน แต่ควรเร็วพอสมควร
หากต้องการใช้สคริปต์เพื่อลบบรรทัดออกจากจุดสิ้นสุดของไฟล์:
./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)
คุณสามารถลองหัว GNU
head -n -2 file
head: illegal line count -- -2
ฉันเห็น Debian Squeeze / ระบบการทดสอบของฉัน (แต่ไม่ใช่ Lenny / เสถียร) รวมคำสั่ง "truncate" ซึ่งเป็นส่วนหนึ่งของแพ็คเกจ "coreutils"
ด้วยคุณสามารถทำสิ่งที่ชอบ
truncate --size=-160 myfile
เพื่อลบ 160 ไบต์จากจุดสิ้นสุดของไฟล์ (เห็นได้ชัดว่าคุณต้องเข้าใจจำนวนตัวอักษรที่คุณต้องการลบ)
dd
สคริปต์แบบง่ายจะทำเช่นนั้น (คุณต้องระบุอินพุตออฟเซ็ตเพื่อรับกิโลไบต์ล่าสุดจากนั้นใช้tail -2 | LANG= wc -c
หรือ sth เช่นนั้น)
tail
มีประสิทธิภาพสำหรับไฟล์ขนาดใหญ่เช่นกัน - สามารถใช้tail | wc -c
คำนวณจำนวนไบต์ที่จะตัดแต่ง
ปัญหาของ sed คือมันเป็นตัวแก้ไขสตรีม - มันจะประมวลผลไฟล์ทั้งหมดแม้ว่าคุณจะต้องการแก้ไขในตอนท้ายเท่านั้น ไม่ว่าจะเกิดอะไรขึ้นคุณกำลังสร้างไฟล์ 400GB ใหม่ทีละบรรทัด เครื่องมือแก้ไขใด ๆ ที่ทำงานกับไฟล์ทั้งหมดอาจมีปัญหานี้
หากคุณทราบจำนวนบรรทัดคุณสามารถใช้head
แต่จะสร้างไฟล์ใหม่แทนการแก้ไขไฟล์ที่มีอยู่ คุณอาจได้รับความเร็วจากความเรียบง่ายของแอ็คชั่นฉันเดา
คุณอาจโชคดีกว่าsplit
ในการแบ่งไฟล์ออกเป็นชิ้นเล็ก ๆ แก้ไขไฟล์สุดท้ายแล้วใช้cat
เพื่อรวมไฟล์เหล่านั้นอีกครั้ง แต่ฉันไม่แน่ใจว่ามันจะดีกว่านี้หรือไม่ ฉันจะใช้นับไบต์แทนเส้นมิฉะนั้นมันอาจจะไม่เร็วเลย - คุณจะยังคงสร้างไฟล์ 400GB ใหม่
ลองใช้ VIM ... ฉันไม่แน่ใจว่ามันจะใช้กลอุบายหรือไม่เพราะฉันไม่เคยใช้มันในไฟล์ขนาดใหญ่เช่นนี้ แต่ฉันเคยใช้มันในไฟล์ที่มีขนาดเล็กกว่าในอดีตให้ลองดู
ไฟล์ประเภทใดและในรูปแบบใด อาจจะง่ายกว่าที่จะใช้สิ่งที่ต้องการ Perl ขึ้นอยู่กับชนิดของไฟล์ - ข้อความกราฟิกไบนารี? มีการจัดรูปแบบอย่างไร - CSV, TSV ...
หากคุณรู้ขนาดของไฟล์เป็นไบต์ (400000000160 พูด) และคุณรู้ว่าคุณจำเป็นต้องลบอักขระ 160 ตัวเพื่อตัดสองบรรทัดสุดท้ายดังนั้น
dd if=originalfile of=truncatedfile ibs=1 count=400000000000
ควรทำเคล็ดลับ เป็นเวลานานแล้วที่ฉันใช้ความโกรธในทางที่ผิด; ฉันดูเหมือนจะจำสิ่งต่าง ๆ ได้เร็วขึ้นหากคุณใช้ขนาดบล็อกที่ใหญ่กว่า แต่ไม่ว่าคุณจะทำเช่นนั้นได้หรือไม่ขึ้นอยู่กับว่าเส้นที่คุณต้องการจะลดลงนั้นมีความหลากหลาย
dd มีตัวเลือกอื่น ๆ ในการแพ็ดบันทึกข้อความเป็นขนาดคงที่ซึ่งอาจเป็นประโยชน์ในการผ่านเบื้องต้น
หากคำสั่ง "truncate" ไม่มีอยู่ในระบบของคุณ (ดูคำตอบอื่น ๆ ของฉัน) ให้ดูที่ "man 2 truncate" สำหรับการเรียกระบบเพื่อตัดไฟล์ให้มีความยาวตามที่กำหนด
เห็นได้ชัดว่าคุณต้องรู้จำนวนตัวอักษรที่คุณต้องการตัดทอนไฟล์เป็น (ขนาดลบความยาวของปัญหาสองบรรทัดอย่าลืมนับตัวอักษร cr / lf)
และทำการสำรองไฟล์ก่อนที่จะลอง!
หากคุณต้องการโซลูชันแบบ unix คุณสามารถมีการตัดและบันทึกบรรทัดแบบโต้ตอบโดยใช้โค้ดสามบรรทัด (ทดสอบบน Mac และ Linux)
การตัดบรรทัดที่มีขนาดเล็กและปลอดภัยสไตล์ยูนิกซ์ (ขอให้ยืนยัน):
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` )"
โซลูชันนี้ใช้เครื่องมือยูนิกซ์ทั่วไปเพียงบางส่วน แต่ยังคงใช้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
#! / bin / ดวลจุดโทษ ed "$ 1" << ที่นี่ $ d d W ที่นี่
มีการเปลี่ยนแปลงเกิดขึ้น นี่คือเรียบง่ายและมีประสิทธิภาพมากกว่าสคริปต์หลาม
ed
ใช้เวลานานกว่าการรันสคริปต์ Python ของฉันถึง 100 เท่า ฉันสามารถจินตนาการได้ว่าความแตกต่างของไฟล์ OP จะมากขึ้นเท่าไหร่ซึ่งใหญ่กว่า 7000 เท่า
แก้ไขคำตอบที่ยอมรับเพื่อแก้ไขปัญหาที่คล้ายกัน อาจปรับแต่งเล็กน้อยเพื่อลบบรรทัดที่ 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()
คุณสามารถใช้ Vim ในโหมด Ex:
ex -sc '-,d|x' file
-,
เลือก 2 บรรทัดสุดท้าย
d
ลบ
x
บันทึกและปิด
head -n -2 file