2012-10-06 18 views
9

Tengo ~ 200 archivos de texto cortos (50kb) que tienen un formato similar. Quiero encontrar una línea en cada uno de esos archivos que contiene una cierta cadena y luego escribir esa línea más las siguientes tres líneas (pero no el resto de las líneas en el archivo) a otro archivo de texto. Estoy tratando de enseñarme Python para hacer esto y he escrito un pequeño guión muy simple y crudo para probarlo. Estoy utilizando la versión 2.6.5, y ejecutar la secuencia de comandos de la terminal de Mac:Usando python para escribir líneas específicas de un archivo a otro archivo

#!/usr/bin/env python 

f = open('Test.txt') 

Lines=f.readlines() 
searchquery = 'am\n' 
i=0 

while i < 500: 
    if Lines[i] == searchquery: 
     print Lines[i:i+3] 
     i = i+1 
    else: 
     i = i+1 
f.close() 

Esto más o menos funciona e imprime el resultado en la pantalla. Pero me gustaría imprimir las líneas en un archivo nuevo en su lugar, por lo que hemos probado algo como esto:

f1 = open('Test.txt') 
f2 = open('Output.txt', 'a') 

Lines=f1.readlines() 
searchquery = 'am\n' 
i=0 

while i < 500: 
if Lines[i] == searchquery: 
    f2.write(Lines[i]) 
    f2.write(Lines[i+1]) 
    f2.write(Lines[i+2]) 
    i = i+1 
else: 
    i = i+1 
f1.close() 
f2.close() 

Sin embargo, nada se escribe en el archivo. También probé

from __future__ import print_function 
print(Lines[i], file='Output.txt') 

y tampoco puedo hacer que funcione. Si alguien puede explicar lo que estoy haciendo mal o ofrecer algunas sugerencias sobre lo que debería probar, estaría muy agradecido. Además, si tiene alguna sugerencia para mejorar la búsqueda, también los agradecería. He estado usando un archivo de prueba donde la cadena que quiero encontrar es el único texto en la línea, pero en mis archivos reales la cadena que necesito todavía está al principio de la línea, pero seguida de un montón de otro texto, por lo que Creo que la forma en que tengo las cosas puestas ahora tampoco funcionará realmente.

Gracias, y lo siento si esta es una pregunta muy básica!

+2

En el segundo ejemplo, parece que su cuerpo de bucle no está sangrado ... ¿se trata de un error de copiar/pegar o de lo que realmente tiene? – Collin

+2

Probablemente debería examinar la función 'enumerate' y' for x in iterable'. –

+0

@Collin Tienes razón, el problema fue la sangría.¡Probablemente miré ese código durante dos horas y nunca me di cuenta! ¡Gracias! – Andreanna

Respuesta

17

Como ha señalado @ajon, no creo que haya nada fundamentalmente malo con su código, excepto la sangría. Con la sangría arreglada, funciona para mí. Sin embargo, hay un par de oportunidades para mejorar.

1) En Python, la forma estándar de iterar sobre las cosas es mediante el uso de for loop. Al usar un bucle for, no necesita definir variables de contador de bucle y realizar un seguimiento de ellas usted mismo para iterar sobre cosas. En su lugar, escribe algo como esto

for line in lines: 
    print line 

para recorrer todos los elementos en una lista de cadenas e imprimirlos.

2) En la mayoría de los casos, así es como se verán sus lazos for. Sin embargo, hay situaciones en las que realmente desea realizar un seguimiento del recuento de bucles. Su caso es una situación así, porque no solo necesita esa línea sino también las siguientes tres, y por lo tanto necesita usar el contador para indexar (lst[i]). Para eso hay enumerate(), que devolverá una lista de elementos y cuyo índice puede buclear.

for i, line in enumerate(lines): 
    print i 
    print line 
    print lines[i+7] 

Si se va a mantener un registro manual del contador del bucle como en el ejemplo, hay dos cosas:

3) Eso i = i+1 debe ser movido fuera de los if y else bloques. Lo estás haciendo en ambos casos, así que ponlo después del if/else. En su caso el bloque else entonces no hace nada más, y puede ser eliminada:

while i < 500: 
    if Lines[i] == searchquery: 
     f2.write(Lines[i]) 
     f2.write(Lines[i+1]) 
     f2.write(Lines[i+2]) 
    i = i+1 

4) Ahora, esto causará un IndexError con los archivos de menos de 500 líneas. En lugar de codificar con dificultad una cuenta de bucle de 500, debe usar la longitud real de la secuencia sobre la que está iterando. len(lines) le dará esa longitud. Pero en lugar de usar un bucle while, use un bucle for y range(len(lst)) para recorrer una lista del rango de cero a len(lst) - 1.

for i in range(len(lst)): 
    print lst[i] 

5)open() se puede utilizar como un context manager que se encarga de cerrar archivos para usted. Los administradores de contexto son un concepto bastante avanzado, pero son bastante simples de usar si ya están provistos para usted. Al hacer algo como esto

with open('test.txt') as f: 
    f.write('foo') 

se abrirá el archivo y accesible para usted como f dentro de ese bloque with. Después de salir del bloque, el archivo se cerrará automáticamente, por lo que no puede olvidarse de cerrar el archivo.

En su caso está abriendo dos archivos. Esto se puede hacer utilizando sólo dos with declaraciones y anidarlas

with open('one.txt') as f1: 
    with open('two.txt') as f2: 
     f1.write('foo') 
     f2.write('bar') 

o, en Python 2.7/3.x Python, anidando dos gestor de contexto en un solo with declaración:

with open('one.txt') as f1, open('two.txt', 'a') as f2: 
     f1.write('foo') 
     f2.write('bar') 

6) Según el sistema operativo en el que se creó el archivo, las terminaciones de línea son diferentes. En las plataformas tipo UNIX es \n, Mac antes de OS X usa \r, y Windows usa \r\n. De modo que Lines[i] == searchquery no coincidirá con los finales de línea de Mac o Windows. file.readline() puede tratar con los tres, pero como mantiene las terminaciones de línea al final de la línea, la comparación fallará. Esto se resuelve mediante el uso de str.strip(), que se tira de la cadena de todos los espacios en blanco al principio y al final, y comparar un patrón de búsqueda sin la línea final para que:

searchquery = 'am' 
# ... 
      if line.strip() == searchquery: 
       # ... 

(Leyendo el archivo usando file.read() y usar str.splitlines() sería otra alternativa.)

Pero, ya que ha mencionado la cadena de búsqueda en realidad aparece al principio de la línea, vamos a hacer eso, mediante el uso de str.startswith():

if line.startswith(searchquery): 
    # ... 

7) La guía oficial de estilo para Python, PEP8, recomienda usar CamelCase para las clases, lowercase_underscore para casi todo lo demás (variables, funciones, atributos, métodos, módulos, paquetes). Por lo tanto, en lugar de Lines, use lines. Este es definitivamente un punto menor en comparación con los otros, pero aún vale la pena llegar desde el principio.


Por lo tanto, teniendo en cuenta todas las cosas que iba a escribir el código de la siguiente manera:

searchquery = 'am' 

with open('Test.txt') as f1: 
    with open('Output.txt', 'a') as f2: 
     lines = f1.readlines() 
     for i, line in enumerate(lines): 
      if line.startswith(searchquery): 
       f2.write(line) 
       f2.write(lines[i + 1]) 
       f2.write(lines[i + 2]) 

Como @TomK señaló, todo este código se supone que si los partidos cadena de búsqueda, hay por lo menos dos líneas siguiéndolo. Si no puede confiar en esa suposición, tratar con ese caso utilizando un bloque try...except como lo sugirió @poorsod es el camino correcto.

+2

Guau, esto es increíblemente útil. Gracias por las explicaciones detalladas! – Andreanna

2

Creo que su problema son las pestañas del archivo inferior.

Necesitas una sangría de si Lines[i] hasta después i=i+1 tales como:

while i < 500: 
    if Lines[i] == searchquery: 
     f2.write(Lines[i]) 
     f2.write(Lines[i+1]) 
     f2.write(Lines[i+2]) 
     i = i+1 
    else: 
     i = i+1 
1

ajon tiene la respuesta correcta, pero mientras busque orientación, su solución no aprovecha las construcciones de alto nivel que Python puede ofrecer. ¿Qué tal:

searchquery = 'am\n' 

with open('Test.txt') as f1: 
    with open(Output.txt, 'a') as f2: 

    Lines = f1.readlines() 

    try: 
     i = Lines.index(searchquery) 
     for iline in range(i, i+3): 
     f2.write(Lines[iline]) 
    except: 
     print "not in file" 

Las dos declaraciones "con" cerrarán automáticamente los archivos al final, incluso si ocurre una excepción.

A sigue siendo mejor solución sería la de evitar la lectura en todo el archivo a la vez (que sabe lo grande que podría ser?) Y, en cambio, la línea de proceso por línea, usando iteración en un objeto de archivo:

with open('Test.txt') as f1: 
    with open(Output.txt, 'a') as f2: 
     for line in f1: 
     if line == searchquery: 
      f2.write(line) 
      f2.write(f1.next()) 
      f2.write(f1.next()) 

Todos estos asumen que hay al menos dos líneas adicionales más allá de su línea objetivo.

+0

"Todos estos asumen que hay al menos dos líneas adicionales más allá de su línea objetivo". - vale la pena señalar que la forma de lidiar con esta situación es con un bloque ['try ... except'] (http://docs.python.org/reference/compound_stmts.html#the-try-statement) : 'try: f2.write (f1.next()); excepto StopIteration: pass' o similar. –

+0

Debería _nunca_ usar declaraciones desnudas 'excepto:', siempre liste las excepciones que desea capturar explícitamente ('IndexError' en este caso). Usar un 'excepto' puro puede llevar a atrapar cosas que no se le ocurrieron, y debe manejar de manera diferente el caso que estaba esperando. En ese caso, debe levantarse la excepción, haciendo que su código se detenga en lugar de continuar y posiblemente cause daños. –

1

¿Ha intentado utilizar algo que no sea 'Output.txt' para evitar cualquier problema relacionado con el sistema de archivos como problema?

¿Qué tal una ruta absoluta para evitar problemas funky imprevistos al diagnosticar esto.

Este consejo es simplemente desde el punto de vista del diagnóstico. También vea el OS X dtrace y dtruss.

Ver: Equivalent of strace -feopen <command> on mac os X

0

línea de escritura por la línea puede ser lento cuando se trabaja con grandes volúmenes de datos. Puede acelerar las operaciones de lectura/escritura leyendo/escribiendo un montón de líneas a la vez.

from itertools import slice 

f1 = open('Test.txt') 
f2 = open('Output.txt', 'a') 

bunch = 500 
lines = list(islice(f1, bunch)) 
f2.writelines(lines) 

f1.close() 
f2.close() 

En caso de que sus líneas son demasiado largos y en función de su sistema, puede que no sea capaz de poner 500 líneas en una lista. Si ese es el caso, debe reducir el tamaño bunch y tener tantos pasos de lectura/escritura como sea necesario para escribir todo.

Cuestiones relacionadas