homer512 icon

proper tail py

homer512 | PRO | 06/17/14 07:18:31 PM UTC | 0 ⭐ | 744 👁️ | Never ⏰ | []
Python |

3.54 KB

|

None

|

0 👍

/

0 👎

#!/usr/bin/python2
 
 
"""Mimics UNIX tail, especially its behavior when dealing with large files
 
The I/O operations have nearly the same pattern as tail
"""
 
import collections
import sys
import errno
 
 
def tail_unseekable(filein, count):
    """Fallback for special files like pipes or terminals
 
    Arguments:
    filein -- file-like object opened for reading
    count -- number of lines to be written to stdout
    """
    sys.stdout.writelines(collections.deque(filein, count))
 
 
def read_blocks(filein, positions, endpos):
    """Yields strings of 8192 bytes length from filein
 
    Arguments:
    filein -- seekable file opened for reading
    positions -- iterable of seek positions from which to read
    endpos -- a position beyond which shall not be read
    """
    blocks = ((blockpos, min(8192, endpos - blockpos)) for blockpos
              in positions)
    for blockpos, blocklen in blocks:
        filein.seek(blockpos)
        yield filein.read(blocklen)
 
 
def count_lines(filein, count, endpos):
    """Reads the given file backwards, stopping after counting count lnes
 
    Arguments:
    filein -- seekable file opened for reading. After returning, the file
              position will be right after the returned data block
    count -- number of lines to find
    endpos -- a position beyond which shall not be read
 
    Returns:
    (linecount, blockdata)
    linecount -- the number of lines actually counted. This can be higher than
                 count or lower (when the file is too short)
    blockdata -- the last block read from file, included in linecount
    """
    linecount = 0
    blockdata = None
    blockpositions = reversed(xrange(0, endpos, 8192))
    for blockdata in read_blocks(filein, blockpositions, endpos):
        blocklines = blockdata.count('\n')
        linecount += blocklines
        if linecount >= count:
            break
    return (linecount, blockdata)
 
 
def print_partial(blockdata, actual_count, wanted_count):
    """Prints the tail of blockdata to stdout, skipping superfluous lines
 
    Arguments:
    blockdata -- a string containing lines
    actual_count -- number of lines in blockdata (and beyond)
    wanted_count -- number of lines that shall be written
    """
    toomuch = actual_count - wanted_count
    startpos = 0
    for _ in xrange(toomuch):
        startpos = blockdata.index('\n', startpos) + 1
    sys.stdout.write(buffer(blockdata, startpos))
 
 
def print_to_pos(filein, endpos):
    """Prints filein to stdout from the current position to endpos"""
    blockpos = filein.tell()
    wholeblocks, remainder = divmod(endpos - blockpos, 8192)
    blocklens = [8192] * wholeblocks
    blocklens.append(remainder)
    blocks = (filein.read(blocklen) for blocklen in blocklens)
    for block in blocks:
        sys.stdout.write(block)
    # sys.stdout.writelines(blocks) # works, too, but buffers output
 
 
def tail(filein, count):
    """Writes the last count lines from filein to stdout"""
    try:
        filein.seek(0, 2)
    except IOError as err:
        if err.errno == errno.ESPIPE:
            tail_unseekable(filein, count)
            return
        else:
            raise
    endpos = filein.tell()
    linecount, blockdata = count_lines(filein, count, endpos)
    print_partial(blockdata, linecount, count)
    print_to_pos(filein, endpos)
 
 
def main():
    filename = sys.argv[1]
    count = 100
    with open(filename, 'r', 8192) as fd:
        tail(fd, count)
    return
 
 
if __name__ == '__main__':
    main()

Comments