views:

364

answers:

4

I have a very long text file that I'm trying to process using Python.

However, the following code:

for line in open('textbase.txt', 'r'):
    print 'hello world'

produces only the following output:

hello world

It's as though Python thinks the file is only one line long, though it is many thousands of lines long, when viewed in a text editor. Examining it on the command line using the file command gives:

$ file textbase.txt
textbase.txt: Big-endian UTF-16 Unicode English text, with CR line terminators

Is something wrong? Do I need to change the line terminators?

+6  A: 

You'll probably find it's the "with CR line terminators" that gives the game away. If you're working on a platform that uses newlines as line terminators, it will see your file as one big honkin' line.

Change your input file so that it uses the correct line terminators. Your editor is probably more forgiving than your Python implementation.

The CR line endings are a Mac thing as far as I'm aware and you can use the U mode modifier to open to auto-detect based on the first line terminator found.

paxdiablo
`Nail+head` combo me thinks. +1.
Wim Hollebrandse
Thanks. Any idea what I need to change them to?
AP257
I would say `\n`.
Wim Hollebrandse
could either be CR+LF (Windows) or LF (but this would be on older macs).
Adriano Varoli Piazza
@Adriano: CR is the line terminator for older macs. It's LF for all *nix systems.
ΤΖΩΤΖΙΟΥ
@TZOOTZIOY: I shouldn't have made that mistake. Brown paper bag time.
Adriano Varoli Piazza
A: 

open() returns a file object. You need to use:

for line in open('textbase.txt', 'r').readlines():
    print line
Paul
This isn't necessary, as the open file object behaves like an iterator.
Ben James
Makes no difference, sorry...
AP257
Ah...good point. Hadn't aprreciated that.
Paul
Yeah, sorry I upvoted this before realising my mistake.
Kragen
+20  A: 

According to the documentation for open(), you should add a U to the mode:

open('textbase.txt', 'Ur')

This enables "universal newlines", which normalizes them to \n in the strings it gives you.

However, the correct thing to do is to decode the UTF-16BE into Unicode objects first, before translating the newlines. Otherwise, a chance 0x0d byte could get erroneously turned into a 0x0a, resulting in

UnicodeDecodeError: 'utf16' codec can't decode byte 0x0a in position 12: truncated data.

Python's codecs module supplies an open function that can decode Unicode and handle newlines at the same time:

import codecs
for line in codecs.open('textbase.txt', 'Ur', 'utf-16be'):
    ...

If the file has a byte order mark (BOM) and you specify 'utf-16', then it detects the endianness and hides the BOM for you. If it does not (since the BOM is optional), then that decoder will just go ahead and use your system's endianness, which probably won't be good.

Specifying the endianness yourself (with 'utf-16be') will not hide the BOM, so you might wish to use this hack:

import codecs
firstline = True
for line in codecs.open('textbase.txt', 'Ur', 'utf-16be'):
    if firstline:
        firstline = False
        line = line.lstrip(u'\ufeff')

See also: Python Unicode HOWTO

jleedev
+1 for the solution rather than just the analysis (as in my answer) - you were too fast for me :-)
paxdiablo
Solves the problem, python now sees all the lines. Thank you so much: I love this site :)
AP257
@AP257: do they also decode properly? If it's really UTF-16BE, there'll be zero byte in front of every line, since Python's file object is encoding-unaware and just splits on newline characters. IMHO, you'll have to decode the file (by using the codecs module) properly before splitting into lines is possible.
Torsten Marek
@Torsten Since we're using big endian, the nulls come before the newlines, so a code point will not get chopped in half. That's a good point however. http://bugs.python.org/issue691291
jleedev
@jleedev: Right, my bad. I confused the test files in my experiments.
Torsten Marek
@Torsten Thanks for the tip anyway; I've updated this to use the codecs module.
jleedev
@jleedev: Welcome. The source for my confusion was that recode (the tool) by default creates UTF-16BE (of course including the BOM) when I specify UTF-16 (with order) as the target. I thought it'd create whatever is the platform endianness (LE, since it's x86-64).
Torsten Marek
+1  A: 

Hi,

it looks like your file has lines terminated only by CR, and Python is probably expecting LF or CRLF. Try using the 'universal newline':

for line in open('textbase.txt', 'rU'):
    print 'hello world'

http://docs.python.org/library/functions.html?highlight=open#open

Miron Brezuleanu