2011-08-23 45 views
14

Tôi cần trích xuất dòng cuối cùng từ một số tệp văn bản rất lớn (vài trăm megabyte) để nhận dữ liệu nhất định. Hiện tại, tôi đang sử dụng python để chuyển qua tất cả các dòng cho đến khi tệp rỗng và sau đó tôi xử lý dòng cuối cùng được trả về, nhưng tôi chắc chắn có một cách hiệu quả hơn để thực hiện việc này.Tìm dòng cuối cùng trong một tệp văn bản

Cách tốt nhất để truy xuất dòng cuối cùng của tệp văn bản bằng python là gì?

+0

Đây có phải là một câu hỏi Python hay câu trả lời bằng cách sử dụng awk hoặc sed chỉ là tốt? –

+1

Bạn cần cung cấp một phần thông tin quan trọng (nhiều câu trả lời đã bỏ qua hoàn toàn): mã hóa tệp của bạn. –

+0

Chỉ mã hóa nhiều byte (ví dụ: UTF-16 hoặc UTF-32) sẽ phá vỡ các thuật toán được cung cấp. –

Trả lời

-1
lines = file.readlines() 
fileHandle.close() 
last_line = lines[-1] 
+1

Gah! Đừng bao giờ làm 'dòng [len (dòng) -1]'. Đó là một hoạt động 'O (n)'. 'dòng [-1]' sẽ nhận được dòng cuối cùng. Bên cạnh đó, điều này không tốt hơn cách tiếp cận mà anh ta đã sử dụng. –

+0

Rất tiếc, lỗi của tôi! Phương pháp này thực sự là hiệu quả hơn mặc dù. –

+7

@gddc: 'dòng [len (dòng) -1]' không phải là O (n) (trừ khi 'dòng' là một kiểu do người dùng định nghĩa với việc thực hiện O (n)' __len__', nhưng đó không phải là trường hợp ở đây). Trong khi đó là kiểu xấu, 'dòng [len (dòng) -1]' có chi phí thời gian chạy giống hệt như 'dòng [-1]'; sự khác biệt duy nhất là liệu việc tính toán chỉ mục được thực hiện một cách rõ ràng trong kịch bản hay ngầm bởi thời gian chạy. –

14

Không cách thẳng về phía trước, nhưng có lẽ nhanh hơn nhiều so với một thực hiện Python đơn giản:

line = subprocess.check_output(['tail', '-1', filename]) 
+1

bạn sẽ muốn thêm [0: -1] vào cuối, bằng cách nào đó, thêm một '\ n' ở cuối ... –

+1

Nó không phải là một giải pháp rất trăn –

5

Sử dụng phương pháp seek của tập tin với một tiêu cực bù đắp và whence=os.SEEK_END để đọc một khối từ cuối tệp. Tìm kiếm khối đó cho (các) ký tự cuối dòng cuối cùng và lấy tất cả các ký tự sau nó. Nếu không có dòng kết thúc, sao lưu xa hơn và lặp lại quá trình.

def last_line(in_file, block_size=1024, ignore_ending_newline=False): 
    suffix = "" 
    in_file.seek(0, os.SEEK_END) 
    in_file_length = in_file.tell() 
    seek_offset = 0 

    while(-seek_offset < in_file_length): 
     # Read from end. 
     seek_offset -= block_size 
     if -seek_offset > in_file_length: 
      # Limit if we ran out of file (can't seek backward from start). 
      block_size -= -seek_offset - in_file_length 
      if block_size == 0: 
       break 
      seek_offset = -in_file_length 
     in_file.seek(seek_offset, os.SEEK_END) 
     buf = in_file.read(block_size) 

     # Search for line end. 
     if ignore_ending_newline and seek_offset == -block_size and buf[-1] == '\n': 
      buf = buf[:-1] 
     pos = buf.rfind('\n') 
     if pos != -1: 
      # Found line end. 
      return buf[pos+1:] + suffix 

     suffix = buf + suffix 

    # One-line file. 
    return suffix 

Lưu ý rằng điều này sẽ không hoạt động trên những thứ không hỗ trợ seek, như stdin hoặc ổ cắm. Trong những trường hợp đó, bạn bị mắc kẹt khi đọc toàn bộ nội dung (như lệnh tail).

3

Nếu bạn biết chiều dài tối đa của một dòng, bạn có thể làm

def getLastLine(fname, maxLineLength=80): 
    fp=file(fname, "rb") 
    fp.seek(-maxLineLength-1, 2) # 2 means "from the end of the file" 
    return fp.readlines()[-1] 

này hoạt động trên máy tính của cửa sổ của tôi. Nhưng tôi không biết điều gì xảy ra trên các nền tảng khác nếu bạn mở tệp văn bản ở chế độ nhị phân. Chế độ nhị phân là cần thiết nếu bạn muốn sử dụng tìm kiếm().

+2

Và nếu bạn không biết chiều dài dòng tối đa? –

+1

cả câu trả lời này và mike là "đúng cách để làm điều đó", nhưng có vấn đề cho bất cứ điều gì khác hơn so với đơn giản (single byte, ví dụ như ASCII) văn bản mã hóa. unicode có thể có các ký tự nhiều byte, vì vậy trong trường hợp đó (1) bạn không biết độ lệch tương đối tính bằng byte cho độ dài tối đa cho trước trong ký tự và (2) bạn có thể tìm kiếm "trung bình" của ký tự. –

+0

@Adam, bạn thường có thể chọn một số lớn hơn bất kỳ độ dài dòng hợp lý nào ngay cả khi nó không được đảm bảo tối đa.Nếu bạn hoàn toàn không thể đưa ra bất kỳ giả định nào hoặc chấp nhận một đường cắt ngắn, bạn không còn cách nào khác ngoài đọc toàn bộ tệp. –

3

Tìm kiếm ở cuối tệp trừ 100 byte. Hãy đọc và tìm kiếm một dòng mới. Nếu đây không phải là dòng mới, hãy tìm kiếm thêm 100 byte nữa. Lót, rửa sạch, lặp lại. Cuối cùng bạn sẽ tìm thấy một dòng mới. Dòng cuối cùng bắt đầu ngay sau dòng mới đó.

Kịch bản trường hợp tốt nhất bạn chỉ thực hiện một lần đọc 100 byte.

2

Nếu bạn có thể chọn độ dài dòng tối đa hợp lý, bạn có thể tìm đến gần cuối tập tin trước khi bắt đầu đọc.

myfile.seek(-max_line_length, os.SEEK_END) 
line = myfile.readlines()[-1] 
+0

Tôi nghĩ rằng bạn phải đi thêm một byte để tìm kiếm, bởi vì readlines() bao gồm các terminator dòng. – rocksportrocker

0

bạn thể tải các tập tin vào một mmap, sau đó sử dụng mmap.rfind (string [, bắt đầu [, kết thúc]]) để tìm nhân vật EOL cuối cùng thứ hai trong file? Việc tìm kiếm điểm đó trong tệp sẽ hướng bạn đến dòng cuối cùng mà tôi nghĩ.

0

Tính không hiệu quả ở đây không thực sự là do Python, nhưng về bản chất của cách đọc tệp. Cách duy nhất để tìm dòng cuối cùng là đọc tệp và tìm dòng kết thúc. Tuy nhiên, hoạt động tìm kiếm có thể được sử dụng để bỏ qua bất kỳ bù đắp byte nào trong tệp.Do đó, bạn có thể bắt đầu rất gần cuối tệp và lấy các đoạn lớn hơn và lớn hơn khi cần cho đến khi dòng cuối cùng kết thúc được tìm thấy:

from os import SEEK_END 

def get_last_line(file): 
    CHUNK_SIZE = 1024 # Would be good to make this the chunk size of the filesystem 

    last_line = "" 

    while True: 
    # We grab chunks from the end of the file towards the beginning until we 
    # get a new line 
    file.seek(-len(last_line) - CHUNK_SIZE, SEEK_END) 
    chunk = file.read(CHUNK_SIZE) 

    if not chunk: 
     # The whole file is one big line 
     return last_line 

    if not last_line and chunk.endswith('\n'): 
     # Ignore the trailing newline at the end of the file (but include it 
     # in the output). 
     last_line = '\n' 
     chunk = chunk[:-1] 

    nl_pos = chunk.rfind('\n') 
    # What's being searched for will have to be modified if you are searching 
    # files with non-unix line endings. 

    last_line = chunk[nl_pos + 1:] + last_line 

    if nl_pos == -1: 
     # The whole chunk is part of the last line. 
     continue 

    return last_line 
+0

'file.seek (-n, os.SEEK_END)' sẽ tăng 'IOError: [Errno 22] Đối số không hợp lệ' nếu' n' lớn hơn kích thước tệp. –

0

Đây là một giải pháp hơi khác. Thay vì đa dòng, tôi chỉ tập trung vào dòng cuối cùng và thay vì kích thước khối không đổi, tôi có kích thước khối động (gấp đôi). Xem nhận xét để biết thêm thông tin.

# Get last line of a text file using seek method. Works with non-constant block size. 
# IDK if that speed things up, but it's good enough for us, 
# especially with constant line lengths in the file (provided by len_guess), 
# in which case the block size doubling is not performed much if at all. Currently, 
# we're using this on a textfile format with constant line lengths. 
# Requires that the file is opened up in binary mode. No nonzero end-rel seeks in text mode. 
REL_FILE_END = 2 
def lastTextFileLine(file, len_guess=1): 
    file.seek(-1, REL_FILE_END)  # 1 => go back to position 0; -1 => 1 char back from end of file 
    text = file.read(1) 
    tot_sz = 1    # store total size so we know where to seek to next rel file end 
    if text != b'\n':  # if newline is the last character, we want the text right before it 
     file.seek(0, REL_FILE_END) # else, consider the text all the way at the end (after last newline) 
     tot_sz = 0 
    blocks = []   # For storing succesive search blocks, so that we don't end up searching in the already searched 
    j = file.tell()   # j = end pos 
    not_done = True 
    block_sz = len_guess 
    while not_done: 
     if j < block_sz: # in case our block doubling takes us past the start of the file (here j also = length of file remainder) 
      block_sz = j 
      not_done = False 
     tot_sz += block_sz 
     file.seek(-tot_sz, REL_FILE_END)   # Yes, seek() works with negative numbers for seeking backward from file end 
     text = file.read(block_sz) 
     i = text.rfind(b'\n') 
     if i != -1: 
      text = text[i+1:].join(reversed(blocks)) 
      return str(text) 
     else: 
      blocks.append(text) 
      block_sz <<= 1 # double block size (converge with open ended binary search-like strategy) 
      j = j - block_sz  # if this doesn't work, try using tmp j1 = file.tell() above 
    return str(b''.join(reversed(blocks)))  # if newline was never found, return everything read 

Lý tưởng nhất, bạn nên bọc nó trong một lớp LastTextFileLine và theo dõi độ dài trung bình di chuyển. Điều này sẽ cho bạn một len_guess tốt có thể. !

-1

/usr/bin/python

count = 0

f = open ('last_line1', 'r')

cho dòng trong f.readlines():

line = line.strip() 

count = count + 1 

print line 

count in

f.close()

count1 = 0

h = open ('last_line1', 'r')

cho dòng trong h.readlines():

line = line.strip() 

count1 = count1 + 1 

if count1 == count: 

    print line   #------------------------- this is the last line 

h.close()

2
with open('output.txt', 'r') as f: 
    lines = f.read().splitlines() 
    last_line = lines[-1] 
    print last_line 
Các vấn đề liên quan