pgtips
pgtips

Reputation: 1348

How to find and replace line only once when found multiple times in Python

I have three lists: filePath, textToFind, textToReplace. I need to open each file at the given filePath, find a line and replace a line. The lists are always in order and always the same length. Here the code:

for i, path in enumerate(filePath):
   for line in fileinput.input(path, inplace=1):
      sys.stdout.write(line.replace(textToFind[i], textToReplace[i]))

The problem is that textToFind can be found numerous times in the file and so this code replaces all occurrences of the text it finds with the current index position of textToReplace. I need it to break when it finds the item the first time and then move on to the next iteration. How can I do this?

Upvotes: 0

Views: 441

Answers (3)

Jean-François Fabre
Jean-François Fabre

Reputation: 140307

You need to detect when there's something to replace. If found, flag, but continue writing the rest of the lines or you'll truncate the file.

(note that using zip on the 3 lists avoids carrying the index)

for path,find,replace in zip(filePath,textToFind,textToReplace):    
   match_found = False
   for line in fileinput.input(path, inplace=1):
      if match_found:
         # keep writing the rest of lines, unchanged
         sys.stdout.write(line)
      else:
         # try to replace
         rep = line.replace(find, replace)
         sys.stdout.write(rep)
         if line!=rep:
             # flag: don't replace anything till the end
             match_found = True

EDIT: after a small discussion with the author of another answer, I think his 2 loops pattern is better than 1 with a flag, so I borrowed it, saves the need for the flag, must be slightly faster:

for path,find,replace in zip(filePath,textToFind,textToReplace):
   handle = fileinput.input(path, inplace=1)
   for line in handle:
     rep = line.replace(find, replace)
     sys.stdout.write(rep)
     if line!=rep:
        break
   for line in handle:
      sys.stdout.write(line)

Upvotes: 2

Work of Artiz
Work of Artiz

Reputation: 1100

My solution has significantly been reduced in elegance. However I still feel like offering an alternative with a temporary file.

# loop over every path, thing to find and replacement in the lists
for path, needle, replacement in zip(filePath, textToFind, textToReplace):
   with open(path) as read_handle:
       with open(path + '.tmp', 'w+') as write_handle:
            # first print and replace
            for line in read_handle:
                 write_handle.write(line.replace(needle, replacement))

                 # if we found something
                 if needle in line:
                     break # quit this the inner for-loop

            # the remaining lines should be printed without modification
            for line in read_handle:
                  write_handle.write(line)
    # overwrite the file with the temporary file
    shutil.move(path + '.tmp', path)

Upvotes: 2

Moinuddin Quadri
Moinuddin Quadri

Reputation: 48120

As an answers to your actual question:

"How to find and replace line only once when found multiple times in Python".

str.replace() has a optional maxreplace option that limits the number of occurrence to be replaced. As per the document:

string.replace(s, old, new[, maxreplace]):

Return a copy of string s with all occurrences of substring old replaced by new. If the optional argument maxreplace is given, the first maxreplace occurrences are replaced.

For example:

>>> my_test_string = 'Hello Hello Hello'

#                                            v  maxreplace as `1`
>>> my_test_string.replace('Hello', 'World', 1)
'World Hello Hello'
# ^ Only first word is replaced

Upvotes: 1

Related Questions