views:

573

answers:

7

How can one delete the very last line of a file via python?

Example File:

hello
world
foo
bar

Resulatant File:

hello
world
foo

I've created the following code to find the number of lines in the file - but I do not know how to delete the specific line number. I'm new to python - so if there is an easier way - please tell me.

 try:
  file = open("file")
 except IOError:
  print "Failed to read file."
 countLines = len(file.readlines())


EDIT:

Figured it out using a variety of answers. Mostly Strawberry's and something I saw in the web - can't find the link DX.

#!/usr/bin/env python

import os, sys

readFile = open("file")

lines = readFile.readlines()

readFile.close()
w = open("file",'w')

w.writelines([item for item in lines[:-1]])

w.close()
+2  A: 

You could use the above code and then:-

lines = file.readlines()
lines = lines[:-1]

This would give you an array of lines containing all lines but the last one.

Strawberry
Will this work well for large files? E.g. thousands of lines?
Nazarius Kappertaal
@Nazarius, it requires you to read the entire file.
matt b
It might not work well for files bigger than a megabyte or two. Depends on your definition of "well". It should be perfectly fine for any desktop use for a few thousand lines.
Paul McMillan
Well - Within a second or two.
Nazarius Kappertaal
Is there no other way to directly delete a specific line? Or is an array the way to go?
Nazarius Kappertaal
Nazarius: There isn't any way to delete a specific line. You can however truncate a file or append to it. Since you want to delete the last line, you can just truncate.
Laurence Gonsalves
+2  A: 

This doesn't use python, but python's the wrong tool for the job if this is the only task you want. You can use the standard *nix utility head, and run

head -n-1 filename > newfile

which will copy all but the last line of filename to newfile.

Peter
I'd like to keep it cross platform - hence the via python in the question.
Nazarius Kappertaal
A: 

On systems where file.truncate() works, you could do something like this:

file = open('file.txt', 'rb')
pos = next = 0
for line in file:
  pos = next # position of beginning of this line
  next += len(line) # compute position of beginning of next line
file = open('file.txt', 'ab')
file.truncate(pos)

According to my tests, file.tell() doesn't work when reading by line, presumably due to buffering confusing it. That's why this adds up the lengths of the lines to figure out positions. Note that this only works on systems where the line delimiter ends with '\n'.

Laurence Gonsalves
Very dangerous on a platform which uses more than one character for "end of line"... as in Windows.
Peter Hansen
Good point. (That was actually why I was originally going to use tell(), but it doesn't work.) In this case opening the file in binary mode should work.
Laurence Gonsalves
+1  A: 

Assuming you have to do this in Python and that you have a large enough file that list slicing isn't sufficient, you can do it in a single pass over the file:

last_line = None
for line in file:
    if last_line:
        print last_line # or write to a file, call a function, etc.
    last_line = line

Not the most elegant code in the world but it gets the job done.

Basically it buffers each line in a file through the last_line variable, each iteration outputs the previous iterations line.

Dan Head
A: 

Though I have not tested it (please, no hate for that) I believe that there's a faster way of going it. It's more of a C solution, but quite possible in Python. It's not Pythonic, either. It's a theory, I'd say.

First, you need to know the encoding of the file. Set a variable to the number of bytes a character in that encoding uses (1 byte in ASCII). CHARsize (why not). Probably going to be 1 byte with an ASCII file.

Then grab the size of the file, set FILEsize to it.

Assume you have the address of the file (in memory) in FILEadd.

Add FILEsize to FILEadd.

Move backwords (increment by -1*CHARsize), testing each CHARsize bytes for a \n (or whatever newline your system uses). When you reach the first \n, you now have the position of the beginning of the first line of the file. Replace \n with \x1a (26, the ASCII for EOF, or whatever that is one your system/with the encoding).

Clean up however you need to (change the filesize, touch the file).

If this works as I suspect it would, you're going to save a lot of time, as you don't need to read through the whole file from the beginning, you read from the end.

Isaac Hodes
Note that the whole \x1a (aka ^Z aka CTRL-Z aka EOF, which is actually SUB in ASCII) thing is totally last century... very few text files are terminated with an actual SUB character any more, and even those are pretty much limited to Windows/DOS systems. And CPM I think.
Peter Hansen
Ah good point - I wasn't sure if it was still in widespread use... can something else be used to salvage this technique?
Isaac Hodes
A: 

here's another way, without slurping the whole file into memory

p=""
f=open("file")
for line in f:
    line=line.strip()
    print p
    p=line
f.close()
A: 

Here's a more general memory-efficient solution allowing the last 'n' lines to be skipped (like the head command):

import collections, fileinput
def head(filename, lines_to_delete=1):
    queue = collections.deque()
    lines_to_delete = max(0, lines_to_delete) 
    for line in fileinput.input(filename, inplace=True, backup='.bak'):
        queue.append(line)
        if lines_to_delete == 0:
            print queue.popleft(),
        else:
            lines_to_delete -= 1
    queue.clear()
Ned Deily