20

I'd like to read to a dictionary all of the lines in a text file that come after a particular string. I'd like to do this over thousands of text files.

I'm able to identify and print out the particular string ('Abstract') using the following code (gotten from this answer):

for files in filepath:
    with open(files, 'r') as f:
        for line in f:
            if 'Abstract' in line:
                print line;

But how do I tell Python to start reading the lines that only come after the string?

Georgy
  • 12,464
  • 7
  • 65
  • 73
Brian Zelip
  • 2,909
  • 4
  • 33
  • 45

6 Answers6

31

Just start another loop when you reach the line you want to start from:

for files in filepath:
    with open(files, 'r') as f:
        for line in f:
            if 'Abstract' in line:                
                for line in f: # now you are at the lines you want
                    # do work

A file object is its own iterator, so when we reach the line with 'Abstract' in it we continue our iteration from that line until we have consumed the iterator.

A simple example:

gen = (n for n in xrange(8))

for x in gen:
    if x == 3:
        print('Starting second loop')
        for x in gen:
            print('In second loop', x)
    else:
        print('In first loop', x)

Produces:

In first loop 0
In first loop 1
In first loop 2
Starting second loop
In second loop 4
In second loop 5
In second loop 6
In second loop 7

You can also use itertools.dropwhile to consume the lines up to the point you want:

from itertools import dropwhile

for files in filepath:
    with open(files, 'r') as f:
        dropped = dropwhile(lambda _line: 'Abstract' not in _line, f)
        next(dropped, '')
        for line in dropped:
                print(line)
Padraic Cunningham
  • 176,452
  • 29
  • 245
  • 321
  • It works, but it's kinda strange, don't you think? and anyone who doesn't understand how generators work will scratch their head as of *why* it produces correct output. – This company is turning evil. Jan 06 '15 at 19:44
  • @Kroltan, well I presume people looking at python know how python code works. This is pretty basic python – Padraic Cunningham Jan 06 '15 at 19:44
  • Well but I wouldn't be so sure the OP is aware of that. – This company is turning evil. Jan 06 '15 at 19:45
  • this doesnt work for me... it just doesnt work it starts at the beginning every time... the loop is embeded and it still starts at the beginning – Kyle Burkett Apr 17 '16 at 19:42
  • @KyleBurkett, that is simply not possible, whatever you consume from an iterator is gone, if it does not work then you are doing something wrong not the code so maybe debugging your code instead of downvoting might be a better option. – Padraic Cunningham Apr 17 '16 at 19:45
  • @PadraicCunningham, but you are skipping `x==3` line? if there a way to not skip that line too and use it. I have a big file. Sometimes, my script fails due to some reason, so I'm planning to use this functionality and read the last line from the redirected output file, match the records with help of `1st for loop` and from where I get a match, I got the starting point! this way I don't have to parse the whole records, again and again, any suggestion how to consume the x==3 line and not skipping it.! – Anu Jul 05 '19 at 23:09
9

Use a boolean to ignore lines up to that point:

found_abstract = False
for files in filepath:
    with open(files, 'r') as f:
        for line in f:
            if 'Abstract' in line:
                found_abstract = True
            if found_abstract:
                #do whatever you want
8

You can use itertools.dropwhile and itertools.islice here, a pseudo-example:

from itertools import dropwhile, islice

for fname in filepaths:
    with open(fname) as fin:
        start_at = dropwhile(lambda L: 'Abstract' not in L.split(), fin)
        for line in islice(start_at, 1, None): # ignore the line still with Abstract in
            print line
Jon Clements
  • 138,671
  • 33
  • 247
  • 280
6

To me, the following code is easier to understand.

with open(file_name, 'r') as f:
    while not 'Abstract' in next(f):
        pass
    for line in f:
        #line will be now the next line after the one that contains 'Abstract'
eguaio
  • 3,754
  • 1
  • 24
  • 38
  • 1
    I am getting AttributeError: '_io.TextIOWrapper' object has no attribute 'next' – yehudahs Sep 28 '17 at 07:28
  • Hoy are probably using python 3.0. Try `next(f)` instead of `f.next()` and let me know if it worked. – eguaio Sep 28 '17 at 07:56
  • When I use a str variable instead of a hard coded value, I get a stop iteration error :( – Erdss4 Nov 13 '18 at 14:06
  • I don't think that error can be caused by using a string variable. It is probably because the string is not present in file. If it is, perhaps the encoding of the file is introcuding some problems. – eguaio Nov 13 '18 at 14:59
4

Just to clarify, your code already "reads" all the lines. To start "paying attention" to lines after a certain point, you can just set a boolean flag to indicate whether or not lines should be ignored, and check it at each line.

pay_attention = False
for line in f:
    if pay_attention:
        print line
    else:  # We haven't found our trigger yet; see if it's in this line
        if 'Abstract' in line:
            pay_attention = True

If you don't mind a little more rearranging of your code, you can also use two partial loops instead: one loop that terminates once you've found your trigger phrase ('Abstract'), and one that reads all following lines. This approach is a little cleaner (and a very tiny bit faster).

for skippable_line in f:  # First skim over all lines until we find 'Abstract'.
    if 'Abstract' in skippable_line:
        break
for line in f:  # The file's iterator starts up again right where we left it.
    print line

The reason this works is that the file object returned by open behaves like a generator, rather than, say, a list: it only produces values as they are requested. So when the first loop stops, the file is left with its internal position set at the beginning of the first "unread" line. This means that when you enter the second loop, the first line you see is the first line after the one that triggered the break.

Henry Keiter
  • 16,863
  • 7
  • 51
  • 80
1

Making a guess as to how the dictionary is involved, I'd write it this way:

lines = dict()
for filename in filepath:
   with open(filename, 'r') as f:
       for line in f:
           if 'Abstract' in line:
               break
       lines[filename] = tuple(f)

So for each file, your dictionary contains a tuple of lines.

This works because the loop reads up to and including the line you identify, leaving the remaining lines in the file ready to be read from f.

Steve Jessop
  • 273,490
  • 39
  • 460
  • 699