2011-08-23 11 views
14

Necesito extraer la última línea de varios archivos de texto muy grandes (varios cientos de megabytes) para obtener ciertos datos. Actualmente, estoy usando python para recorrer todas las líneas hasta que el archivo esté vacío y luego procesar la última línea devuelta, pero estoy seguro de que hay una forma más eficiente de hacerlo.Búsqueda eficiente de la última línea en un archivo de texto

¿Cuál es la mejor manera de recuperar solo la última línea de un archivo de texto usando python?

+0

¿Es esta una pregunta de Python, o una respuesta usando awk o sed sería igual de buena? –

+1

Debe proporcionar una información vital (que muchas respuestas han ignorado por completo): la codificación de su archivo. –

+0

Solo una codificación multibyte (por ejemplo, UTF-16 o UTF-32) romperá los algoritmos dados. –

Respuesta

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

Gah! Nunca hagas 'líneas [len (líneas) -1]'. Esa es una operación 'O (n)'. 'lines [-1]' obtendrá el último. Además, esto no es mejor que el enfoque que ya está usando. –

+0

Vaya, mi error! Sin embargo, este método en realidad es más eficiente. –

+7

@gddc: 'lines [len (lines) -1]' no es O (n) (a menos que 'lines' sea un tipo definido por el usuario con una implementación O (n) de' __len__', pero ese no es el caso aquí) Si bien es un estilo incorrecto, 'lines [len (lines) -1]' tiene un costo de tiempo de ejecución prácticamente idéntico a 'lines [-1]'; la única diferencia es si el cálculo del índice se realiza explícitamente en secuencia de comandos o implícitamente por el tiempo de ejecución. –

14
No

el camino a seguir recto, pero probablemente mucho más rápido que una simple implementación de Python:

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

querrá agregar un [0: -1] al final, de alguna manera está agregando un '\ n' al final ... –

+1

No es una solución muy pitón –

5

usa el método del archivo seek con un desplazamiento negativo y whence=os.SEEK_END para leer un bloque desde el final del archivo. Busque ese bloque para los últimos caracteres de final de línea y capture todos los caracteres posteriores. Si no hay un final de línea, retroceda más y repita el proceso.

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 

Tenga en cuenta que esto no va a trabajar en cosas que no son compatibles seek, al igual que la entrada estándar o zócalos. En esos casos, estás atrapado leyendo todo (como el comando tail).

3

Si lo hace saber la longitud máxima de una línea, puede hacerlo

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] 

Esto funciona en mi máquina Windows. Pero no sé qué sucede en otras plataformas si abre un archivo de texto en modo binario. El modo binario es necesario si desea usar seek().

+2

Y si no conoce el longitud de línea máxima? –

+1

tanto esta como la respuesta de mike son "la forma correcta de hacerlo", pero tienen problemas para cualquier otra cosa que no sea simple (codificaciones de texto de un solo byte, por ejemplo, ASCII). Unicode puede tener caracteres de varios bytes, por lo que en ese caso (1) no conoce el desplazamiento relativo en bytes para una longitud máxima dada en caracteres y (2) puede buscar en "el medio" de un personaje. –

+0

@ Adam, generalmente puede elegir un número que sea mayor que cualquier longitud de línea razonable, incluso si no es un máximo garantizado.Si no puede hacer ninguna suposición o aceptar una línea truncada, no tiene más remedio que leer todo el archivo. –

3

Busque hasta el final del archivo menos 100 bytes más o menos. Haz una lectura y busca una nueva línea. Si no hay línea nueva, busque otros 100 bytes más o menos. Enjabona, enjuaga, repite. Eventualmente encontrarás una nueva línea. La última línea comienza inmediatamente después de esa nueva línea.

En el mejor de los casos, solo hace una lectura de 100 bytes.

2

Si puede elegir una longitud de línea máxima razonable, puede buscar casi el final del archivo antes de comenzar a leer.

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

Creo que tiene que ir un byte más lejos en la búsqueda, porque readlines() incluye el terminador de línea. – rocksportrocker

0

Podría cargar el archivo en un mmap, a continuación, utilizar mmap.rfind (cadena [, start [, end]]) para encontrar la segunda último carácter EOL en el archivo? Una búsqueda de ese punto en el archivo debería indicarle la última línea que pensaría.

0

La ineficacia aquí no se debe realmente a Python, sino a la naturaleza de cómo se leen los archivos. La única forma de encontrar la última línea es leer el archivo y encontrar los finales de línea. Sin embargo, la operación de búsqueda se puede usar para saltar a cualquier desplazamiento de bytes en el archivo.Puede, por lo tanto, comenzará muy cerca del final del archivo, y apoderarse de trozos más grandes como sea necesario hasta que se encuentre la última final de línea:

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)' levantará 'IOError: [Errno 22] argumento inválido' si' n' es mayor que el tamaño del archivo. –

0

Aquí es una solución ligeramente diferente. En lugar de multilínea, me centré solo en la última línea, y en lugar de un tamaño de bloque constante, tengo un tamaño de bloque dinámico (duplicado). Ver comentarios para más información.

# 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 

Lo ideal sería terminar con esto en un LastTextFileLine clase y realizar un seguimiento de una media móvil de las longitudes de línea. Esto le daría una buena len_guess tal vez. !

-1

/usr/bin/python

count = 0

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

para la línea en f.readlines():

line = line.strip() 

count = count + 1 

print line 

la cantidad de copias

f.close()

COUNT1 = 0

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

para la línea en 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 
Cuestiones relacionadas