46

I have a two requirements .

First Requirement-I want to read the last line of a file and assign the last value to a variable in python.

Second Requirement-

Here is my sample file.

<serviceNameame="demo" wsdlUrl="demo.wsdl" serviceName="demo"/>
<context:property-placeholder location="filename.txt"/>

From this file I want to read the content i.e filename.txt which will be after <context:property-placeholder location= ..And want to assign that value to a variable in python.

Eugene Yarmash
  • 142,882
  • 41
  • 325
  • 378
techi
  • 461
  • 1
  • 4
  • 4
  • See [Read a file in reverse order using python](https://stackoverflow.com/questions/2301789/read-a-file-in-reverse-order-using-python#23646049) – jq170727 Sep 16 '17 at 22:25

10 Answers10

107

A simple solution, which doesn't require storing the entire file in memory (e.g with file.readlines() or an equivalent construct):

with open('filename.txt') as f:
    for line in f:
        pass
    last_line = line

For large files it would be more efficient to seek to the end of the file, and move backwards to find a newline, e.g.:

import os

with open('filename.txt', 'rb') as f:
    try:  # catch OSError in case of a one line file 
        f.seek(-2, os.SEEK_END)
        while f.read(1) != b'\n':
            f.seek(-2, os.SEEK_CUR)
    except OSError:
        f.seek(0)
    last_line = f.readline().decode()

Note that the file has to be opened in binary mode, otherwise, it will be impossible to seek from the end.

Eugene Yarmash
  • 142,882
  • 41
  • 325
  • 378
  • The `seek()` approach is blazingly fast, but not as easy to understand what it's doing. The `for` loop approach is not too slow and easy to understand. However, the `readlines()` approach of the next highest voted answer is slightly faster than the `for` approach here. – Mr. Lance E Sloan Mar 25 '20 at 19:08
  • 4
    @L S If you try it on a large enough file (e.g. a few megabytes), `file.readlines()` will be slower than a simple `for` loop. With larger files, it may also exhaust your available memory. – Eugene Yarmash Mar 29 '20 at 18:52
  • 1
    @EugeneYarmash This is a lovely answer for getting the last line of the file. Typically, I don't want the last line but the second or third last line of a file so I tried to generalise this into a function by changing the -2 to ''-n" but I'm getting variable answers and I think it's because it's in bytes and I haven't quite understood the code properly. n=2 does give the last line and n=3 does actually give me the 2nd last line but n=4 gives me the 9th last line. Do you know what I've done wrong here? – jpmorr May 25 '20 at 10:33
  • 1
    @jpmorr I'd think you wouldn't want to change the -2s, I believe those are moving you back one character at a time (when combined with the f.read(1)... if you change them to larger values, you skip characters, and so would get erratic results. Instead I'd modify the while loop to include a count of matches so it runs until it has matched n number of times. (basically while matches < n: f.seek(-2, os.SEEK_CUR) if f.read(1) != b'\n': matches += 1 – JeopardyTempest Oct 13 '20 at 20:28
  • I wonder if this could be sped up even slightly more in some situations (plus be well-suited to adapt to handle alternate encodings that aren't one byte per character if that can be any issue some deal with??) by instead adding the f.seek returns to a running string as you go rather than doing the readline after. I imagine it might depend upon the situation. – JeopardyTempest Oct 13 '20 at 20:41
  • The seek approach here is great! In case anyone else is looking for a solution for the n-to-last line, I wrote one and added it as an answer. @EugeneYarmash – Jazz Weisman Aug 01 '22 at 15:02
  • This answer is a duplicate of my old answer from 2013, which also has a version that supports edge cases as well as an updated one that use galloping search https://stackoverflow.com/a/18603065/1388027 – Trasp Jul 19 '23 at 22:15
42

Why don't you just read all the lines and store the last line to the variable?

with open('filename.txt', 'r') as f:
    last_line = f.readlines()[-1]
Alex Waygood
  • 6,304
  • 3
  • 24
  • 46
gpopides
  • 718
  • 5
  • 13
  • This is a good approach. It's not very slow, it's easier to understand than a `seek()` approach, and when wrapped in a `with` block, there's no need to explicitly call `close()` on the file. – Mr. Lance E Sloan Mar 25 '20 at 19:10
  • 23
    @LS In what way is this efficient? If your file has millions of lines, you have to read millions of lines. Anything besides `seek` or a method that starts at the end of the file is grossly inefficient. – ThomasFromUganda Apr 16 '20 at 17:33
11

Generalized to read line Nth to last

As many other people have said, for large files, any approach not seeking to the end or otherwise starting at the end of the file is very inefficient. The top answer's seek approach is great though. If anyone else is looking for a solution that reads the nth to last line of the file instead, here is one I wrote. Also very fast and efficient for large files (took under a ms for a 7GB file on the network).

def read_n_to_last_line(filename, n = 1):
    """Returns the nth before last line of a file (n=1 gives last line)"""
    num_newlines = 0
    with open(filename, 'rb') as f:
        try:
            f.seek(-2, os.SEEK_END)    
            while num_newlines < n:
                f.seek(-2, os.SEEK_CUR)
                if f.read(1) == b'\n':
                    num_newlines += 1
        except OSError:
            f.seek(0)
        last_line = f.readline().decode()
    return last_line
Jazz Weisman
  • 434
  • 4
  • 10
6

On systems that have a tail command, you could use tail, which for large files would relieve you of the necessity of reading the entire file.

from subprocess import Popen, PIPE
f = 'yourfilename.txt'
# Get the last line from the file
p = Popen(['tail','-1',f],shell=False, stderr=PIPE, stdout=PIPE)
res,err = p.communicate()
if err:
    print (err.decode())
else:
    # Use split to get the part of the line that you require
    res = res.decode().split('location="')[1].strip().split('"')[0]
    print (res)

For a generic whole last line:

res = res.decode()
print(res)

To adjust the number of lines, alter the tail command.
For the last 10 lines you would use ['tail','-10',f]

From the Nth line to the end: ['tail','-n+10000',f]
where 10,000 is the line you want to read from

Note: the decode() command is only required for python3

res = res.split('location="')[1].strip().split('"')[0]

would work for python2.x

Rolf of Saxony
  • 21,661
  • 5
  • 39
  • 60
6

Example from https://docs.python.org/3/library/collections.html

from collections import deque

def tail(filename, n=10):
    'Return the last n lines of a file'
    with open(filename) as f:
        return deque(f, n) 
Paal Pedersen
  • 1,070
  • 1
  • 10
  • 13
  • 2
    This will read the whole file line by line. This is memory efficient, but won't be quick for large files. – Austin Aug 21 '20 at 18:39
  • how about large files you need to think about how to do this efficiently for large files and small memory fingerprint – Walid Jan 23 '21 at 20:21
4

Do a size check and seek backwards a certain number of bytes from the end of the file if it contains at least that many bytes:

  with open(filename, 'rb') as myfile:
      if os.path.getsize(filename) > 200:
         myfile.seek(-200, 2)
      line = myfile.readlines()[-1].decode("utf-8")

Opening in binary mode is necessary in python3, which can't do nonzero end-relative seeks. myfile.seek(-200, 2) will place the current file pointer 200 characters before the end of the file (2), then the last line [-1] is taken from readlines() and decoded.

Casey Jones
  • 155
  • 9
3

He's not just asking how to read lines in the file, or how to read the last line into a variable. He's also asking how to parse out a substring from the last line, containing his target value.

Here is one way. Is it the shortest way? No, but if you don't know how to slice strings, you should start by learning each built-in function used here. This code will get what you want:

# Open the file
myfile = open("filename.txt", "r")
# Read all the lines into a List
lst = list(myfile.readlines())
# Close the file
myfile.close()
# Get just the last line
lastline = lst[len(lst)-1]
# Locate the start of the label you want, 
# and set the start position at the end 
# of the label:
intStart = lastline.find('location="') + 10
# snip off a substring from the 
# target value to the end (this is called a slice):
sub = lastline[intStart:]
# Your ending marker is now the 
# ending quote (") that is located 
# at the end of your target value.
# Get it's index.
intEnd = sub.find('"')
# Finally, grab the value, using 
# another slice operation.
finalvalue = sub[0:intEnd]
print finalvalue

The print command output should look like this:

filename.txt

Topics covered here:

  • Reading text files
  • Making a Python List of lines from the content, to make it easy to get the last line using the zero-based index len(List) -1.
  • Using find to get index locations of a string within a string
  • Using slice to obtain substrings

All of these topics are in the Python documentation - there is nothing extra here, and no imports are needed to use the built-in functions that were used here.

Cheers,
-=Cameron

0

You can read and edit all the line doing something like:

file = open('your_file.txt', 'r')
read_file = file.readlines()
file.close()

file1 = open('your_file.txt', 'w')

var = 'filename.txt'

for lec in range(len(read_file)):
    if lec == 1:
        file1.write('<context:property-placeholder location="%s"/>' % var)
    else:
        file1.write(read_file[lec])
file1.close()
Mauricio Cortazar
  • 4,049
  • 2
  • 17
  • 27
  • Thanks for the reply. .Will it create a new file(filename.txt) in the present directory. If yes ..I don't want to create a new file in the present directory. – techi Sep 16 '17 at 22:31
  • Thanks for the reply. .Will it create a new file(filename.txt) in the present directory. If yes ..I don't want to create a new file in the present directory. – techi Sep 16 '17 at 22:36
  • no, it wont, is just editing the existing one. To understand, first we read and then we write it. I forgot something, close file1.close() – Mauricio Cortazar Sep 16 '17 at 22:37
0

A variation on Eugene's answer: getting the last line with content (avoids returning a blank line if the file ends with multiple newlines)

def read_last_contentful_line(file):
    f = pathlib.Path(test).open('rb')
    try:  # catch OSError in case of a one line file
        f.seek(-2, os.SEEK_END)
        found_content = False
        while True:
            c = f.read(1)
            if not c.isspace():
                found_content = True
            if found_content and c == b'\n':
                if found_content:
                    break
            f.seek(-2, os.SEEK_CUR)
    except OSError:
        f.seek(0)
    return f.readline().decode()
Connor Clark
  • 671
  • 7
  • 15
0

I find Eugene Yarmash's answer suiting. But in the case of an empty file you might get NameError: name 'line' is not defined. So it is safe to add line = None before iterating through the file. Then check that line is not None when you are intending to use the line.

  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Feb 09 '23 at 18:50