1

I am trying to change a some lines in a text file without affecting the other lines. This is what's inside the text file called "text.txt"

this is  a test1|number1
this is a test2|number2
this is a test3|number2
this is a test4|number3
this is a test5|number3
this is a test6|number4
this is a test7|number5
this is a test8|number5
this is a test9|number5
this is a test10|number5

My objective is to change the line 4 and line 5 but keep the rest same.

mylist1=[]
for lines in open('test','r'):
    a=lines.split('|')
    b=a[1].strip()
    if b== 'number3':
        mylist1.append('{}|{} \n'.format('this is replacement','number7'))
    else:
         mylist1.append('{}|{} \n'.format(a[0],a[1].strip()))
myfile=open('test','w')
myfile.writelines(mylist1)

Even though the code works, I am wondering if there is any better and efficient way to do it? Is it possible to read the file just by line number?

Mel
  • 5,837
  • 10
  • 37
  • 42
Chris Aung
  • 9,152
  • 33
  • 82
  • 127
  • You can't write 'a line' at an arbitrary location, but if you want to know the current line number when reading, you can use `enumerate`, as in `for index, line in enumerate(open('test','r')):`. This may help if you really want to identify lines by number rather than location. BTW: writing ' for __lines__ ' as you do is misleading -- what you get each time through the loop is a __single__ line. – kampu May 17 '13 at 09:12

5 Answers5

11

There is not much you can improve. But you have to write all lines to a new file, either changed or unchanged. Minor improvements would be:

  • using the with statement;
  • avoiding storing lines in a list;
  • writing lines without formatting in the else clause (if applicable).

Applying all of the above:

import shutil
with open('test') as old, open('newtest', 'w') as new:
    for line in old:
        if line.rsplit('|', 1)[-1].strip() == 'number3':
            new.write('this is replacement|number7\n')
        else:
            new.write(line)
shutil.move('newtest', 'test')
Lev Levitsky
  • 63,701
  • 20
  • 147
  • 175
  • You are right. Maybe he can use an in-memory file (module StringIO) or a temp file (module tempfile), but basically he cannot do anything on the original file. – Markon May 17 '13 at 08:50
  • why is this not working when i change "newtest" to "test"? even though i have to rewrite to modify the file, i still need it to have the same name. but thanks a lot for the help – Chris Aung May 17 '13 at 08:58
  • @ChrisAung Because opening the file in write mode truncates it. You immediately lose the old content. So you should either move the file when you finish (see my edit) or use `fileinput` as @jamylak suggested; but if you look at the docs, `fileinput` does the same internally: creates a backup file, then silently removes it. – Lev Levitsky May 17 '13 at 09:01
  • @LevLevitsky `fileinput` uses `self._filename + (self._backup or os.extsep+"bak"))` You should do something similar or use `tempfile.NamedTemporaryFile` so this is thread / process safe – jamylak May 17 '13 at 09:12
3
import fileinput

for lines in fileinput.input('test', inplace=True):
    # inplace=True redirects stdout to a temp file which will
    # be renamed to the original when we reach the end of the file. this
    # is more efficient because it doesn't save the whole file into memeory
    a = lines.split('|')
    b = a[1].strip()
    if b == 'number3':
        print '{}|{} '.format('this is replacement', 'number7')
    else:
        print '{}|{} '.format(a[0], a[1].strip())
jamylak
  • 128,818
  • 30
  • 231
  • 230
2

No. Files are byte-oriented, not line-oriented, and changing the length of a line will not advance the following bytes.

Ignacio Vazquez-Abrams
  • 776,304
  • 153
  • 1,341
  • 1,358
0

try this solution

with open('test', inplace=True) as text_file:
    for line in text_file:
         if line.rsplit('|', 1)[-1].strip() == 'number3':
             print '{}|{} \n'.format('this is replacement', 'number7')
         else:
             print line
oleg
  • 4,082
  • 16
  • 16
0

It's not wholly clear whether your intent is to identify the lines to be replaced by their value, or by their line number.

If the former is your intent, you can get a list of lines like this:

with open('test','r') as f:
    oldlines = f.read().splitlines()

If there's a danger of trailing whitespace, you could also:

Then you can process them like this:

newlines = [ line if not line.strip().endswith('|number3') else 'this is replacement|number7' for line in oldlines]

Open the destination file (I'm assuming you want to overwrite the original, here), and write all the lines:

with open('test','w') as f:
    f.write("\n".join(newlines))

This is a general pattern that's useful for any kind of simple line-filtering.

If you meant to identify the lines by number, you could just alter the 'newlines' line:

 newlines = [ line if i not in (3, 4) else 'this is replacement|number7' for i, line in enumerate(oldlines)]
kampu
  • 1,391
  • 1
  • 10
  • 14