373

How do I search and replace text in a file using Python 3?

Here is my code:

import os
import sys
import fileinput

print("Text to search for:")
textToSearch = input("> ")

print("Text to replace it with:")
textToReplace = input("> ")

print("File to perform Search-Replace on:")
fileToSearch = input("> ")

tempFile = open(fileToSearch, 'r+')

for line in fileinput.input(fileToSearch):
    if textToSearch in line:
        print('Match Found')
    else:
        print('Match Not Found!!')
    tempFile.write(line.replace(textToSearch, textToReplace))
tempFile.close()

input('\n\n Press Enter to exit...')

Input file:

hi this is abcd hi this is abcd
This is dummy text file.
This is how search and replace works abcd

When I search and replace 'ram' by 'abcd' in above input file, it work like a charm. But when I do it vice versa, i.e., replacing 'abcd' by 'ram', some junk characters are left at the end.

Replacing 'abcd' by 'ram':

hi this is ram hi this is ram
This is dummy text file.
This is how search and replace works rambcd
4
  • Can you be a bit more specific when you say "some junk characters are left in the end", what do you see? Commented Jun 17, 2013 at 5:31
  • Updated the question with output what i got. Commented Jun 17, 2013 at 5:42
  • edit text file using Python Commented Sep 4, 2014 at 9:09
  • Here's a good answer implementing map instead of a loop: stackoverflow.com/questions/26986229/…, that's what I went with Commented Feb 13, 2021 at 14:52

23 Answers 23

582

As pointed out by michaelb958, you cannot replace in place with data of a different length because this will put the rest of the sections out of place. I disagree with the other posters suggesting you read from one file and write to another. Instead, I would read the file into memory, fix the data up, and then write it out to the same file in a separate step.

# Read in the file
with open('file.txt', 'r') as file:
  filedata = file.read()

# Replace the target string
filedata = filedata.replace('abcd', 'ram')

# Write the file out again
with open('file.txt', 'w') as file:
  file.write(filedata)

Unless you've got a massive file to work with which is too big to load into memory in one go, or you are concerned about potential data loss if the process is interrupted during the second step in which you write data to the file.

Sign up to request clarification or add additional context in comments.

15 Comments

@JonasStein: No, it shouldn't. The with statement automatically closes the file at the end of the statement block.
@debuti: That would read from one file and write to another; this reads and writes to the same file.
@MartinGrůber: Correct, hence the final paragraph.
Allowed to me only write on the file if necessary (which caused an environment reload on in VS) as opposed to the accepted answer.
@AerinmundFagelson: The 'r' specifies that the file is opened for reading, the 'w' that it is opened for writing. If you omit the flag then it defaults to read only, but I prefer to include it for clarity, especially when - as here - I am doing both variants in quick succession. If you are getting FileNotFoundError then it is trying to read a file that doesn't (yet?) exist.
|
360

fileinput already supports inplace editing. It redirects stdout to the file in this case:

#!/usr/bin/env python3
import fileinput

with fileinput.FileInput(filename, inplace=True, backup='.bak') as file:
    for line in file:
        print(line.replace(text_to_search, replacement_text), end='')

24 Comments

What is the end='' argument supposed to do?
line already has a newline. end is a newline by default, end='' makes print() function do not print additional newline
Don't use fileinput! Consider writing the code to do this yourself instead. Redirecting sys.stdout isn't a great idea, especially if you're doing it without a try..finally like fileinput does. If an exception gets raised, your stdout might never get restored.
@craigds: wrong. fileinput is not a tool for all jobs (nothing is) but there are many cases where it is the right tool e.g., to implement a sed-like filter in Python. Don't use a screwdriver to pound nails.
If you really want to redirect stdout to your file for some reason, it's not hard to do it better than fileinput does (basically, use try..finally or a contextmanager to ensure you set stdout back to it's original value afterwards). The source code for fileinput is pretty eye-bleedingly awful, and it does some really unsafe things under the hood. If it were written today I very much doubt it would have made it into the stdlib.
|
63

As Jack Aidley had posted and jfs pointed out, this code will not work:

# Read in the file
filedata = None
with file = open('file.txt', 'r') :
  filedata = file.read()

# Replace the target string
filedata.replace('ram', 'abcd')

# Write the file out again
with file = open('file.txt', 'w') :
  file.write(filedata)`

But this code will work (I've tested it):

f = open(filein,'r')
filedata = f.read()
f.close()

newdata = filedata.replace("old data","new data")

f = open(fileout,'w')
f.write(newdata)
f.close()

Using this method, filein and fileout can be the same file, because Python 3.3 will overwrite the file upon opening for write.

4 Comments

I believe the difference is here: filedata.replace('ram', 'abcd') Compared to: newdata = filedata.replace("old data","new data") Nothing to do with the "with" statement
1. why would you remove with-statement? 2. As stated in my answer, fileinput can work inplace -- it can replace data in same file (it uses a temporary file internally). The difference is that fileinput does not require to load the whole file into memory.
Just to save others revisiting Jack Aidley's answer, it has been corrected since this answer, so this one is now redundant (and inferior due to losing the neater with blocks).
Not very pythonic. I'd either use a try/finally to make sure that the file is always closed, or the usual with statement, or the fileinput option.
57

You can do the replacement like this

f1 = open('file1.txt', 'r')
f2 = open('file2.txt', 'w')
for line in f1:
    f2.write(line.replace('old_text', 'new_text'))
f1.close()
f2.close()

Comments

29

You can also use pathlib.

from pathlib2 import Path
path = Path(file_to_search)
text = path.read_text()
text = text.replace(text_to_search, replacement_text)
path.write_text(text)

1 Comment

Thanks Yuya. The above solution worked well. Note: You need to take backup of your original file first, since it replaces your original file itself. If you want to repeatedly replace text then you can keep adding last 2 lines as below. text = text.replace(text_to_search, replacement_text) path.write_text(text)
13

(pip install python-util)

from pyutil import filereplace

filereplace("somefile.txt","abcd","ram")

Will replace all occurences of "abcd" with "ram".
The function also supports regex by specifying regex=True

from pyutil import filereplace

filereplace("somefile.txt","\\w+","ram",regex=True)

Disclaimer: I'm the author (https://github.com/MisterL2/python-util)

3 Comments

I had some bad experience with this (it added some characters to the end of the file), so I cannot recommend it, even though a one-liner would be nice.
@Azrael3000 It added characters? I have not seen that happen to me. I would highly appreciate if you opened an issue ony Github so I can fix it github.com/MisterL2/python-util
Thanks for the github issue! Problem has been resolved and is fully working now.
9

Open the file in read mode. Read the file in string format. Replace the text as intended. Close the file. Again open the file in write mode. Finally, write the replaced text to the same file.

try:
    with open("file_name", "r+") as text_file:
        texts = text_file.read()
        texts = texts.replace("to_replace", "replace_string")
    with open(file_name, "w") as text_file:
        text_file.write(texts)
except FileNotFoundError as f:
    print("Could not find the file you are trying to read.")

Comments

7

Late answer, but this is what I use to find and replace inside a text file:

with open("test.txt") as r:
  text = r.read().replace("THIS", "THAT")
with open("test.txt", "w") as w:
  w.write(text)

DEMO

3 Comments

better do a back-up too, just in case any error happens.
@HomeroEsmeraldo That's pretty much common sense and it's out of the scope of this answer.
This works but not really suitable for large files.
3

Your problem stems from reading from and writing to the same file. Rather than opening fileToSearch for writing, open an actual temporary file and then after you're done and have closed tempFile, use os.rename to move the new file over fileToSearch.

1 Comment

Friendly FYI (feel free to edit into the answer): The root cause is not being able to shorten the middle of a file in place. That is, if you search for 5 characters and replace with 3, the first 3 chars of the 5 searched for will be replaced; but the other 2 can't be removed, they'll just stay there. The temporary file solution removes these "leftover" characters by dropping them instead of writing them out to the temporary file.
3

With a single with block, you can search and replace your text:

with open('file.txt','r+') as f:
    filedata = f.read()
    filedata = filedata.replace('abc','xyz')
    f.truncate(0)
    f.write(filedata)

1 Comment

You forgot to seek to the beginning of the file before writing it. truncate doesn't do that and so you will have garbage in the file.
3

Using re.subn it is possible to have more control on the substitution process, such as word splitted over two lines, case-(in)sensitive match. Further, it returns the amount of matches which can be used to avoid waste of resources if the string is not found.

import re

file = # path to file

# they can be also raw string and regex
textToSearch = r'Ha.*O' # here an example with a regex
textToReplace = 'hallo'

# read and replace
with open(file, 'r') as fd:
    # sample case-insensitive find-and-replace
    text, counter = re.subn(textToSearch, textToReplace, fd.read(), re.I)

# check if there is at least a  match
if counter > 0:
    # edit the file
    with open(file, 'w') as fd:
        fd.write(text)

# summary result
print(f'{counter} occurence of "{textToSearch}" were replaced with "{textToReplace}".')

Some regex:

  • add the re.I flag, short form of re.IGNORECASE, for a case-insensitive match
  • for multi-line replacement re.subn(r'\n*'.join(textToSearch), textToReplace, fd.read()), depending on the data also '\n{,1}'. Notice that for this case textToSearch must be a pure string, not a regex!

Comments

2

My variant, one word at a time on the entire file.

I read it into memory.

def replace_word(infile,old_word,new_word):
    if not os.path.isfile(infile):
        print ("Error on replace_word, not a regular file: "+infile)
        sys.exit(1)

    f1=open(infile,'r').read()
    f2=open(infile,'w')
    m=f1.replace(old_word,new_word)
    f2.write(m)

Comments

2

Besides the answers already mentioned, here is an explanation of why you have some random characters at the end:
You are opening the file in r+ mode, not w mode. The key difference is that w mode clears the contents of the file as soon as you open it, whereas r+ doesn't.
This means that if your file content is "123456789" and you write "www" to it, you get "www456789". It overwrites the characters with the new input, but leaves any remaining input untouched.
You can clear a section of the file contents by using truncate(<startPosition>), but you are probably best off saving the updated file content to a string first, then doing truncate(0) and writing it all at once.
Or you can use my library :D

Comments

2

I got the same issue. The problem is that when you load a .txt in a variable you use it like an array of string while it's an array of character.

swapString = []
with open(filepath) as f: 
    s = f.read()
for each in s:
    swapString.append(str(each).replace('this','that'))
s = swapString
print(s)

Comments

2

You can use sed or AWK or grep in Python (with some restrictions). Here is a very simple example. It changes banana to bananatoothpaste in the file. You can edit and use it. (I tested it and it worked... Note: if you are testing under Windows, you should install the "sed" command and set the path first)

import os

file = "a.txt"
oldtext = "Banana"
newtext = " BananaToothpaste"
os.system('sed -i "s/{}/{}/g" {}'.format(oldtext, newtext, file))
#print(f'sed -i "s/{oldtext}/{newtext}/g" {file}')
print('This command was applied:  sed -i "s/{}/{}/g" {}'.format(oldtext, newtext, file))

If you want to see results on the file directly apply: "type" for Windows and "cat" for Linux:

#### For Windows:
os.popen("type " + file).read()

#### For Linux:
os.popen("cat " + file).read()

2 Comments

Use subprocess module instead. os.system, and os.popen start shell unnecessary here. subprocess.run(["sed", f"{old}/{new}/g", filename])
@jfs Thank you for your updated and modern method suggestion. Feel free to edit my response according to your recommendation.
1

I have done this:

#!/usr/bin/env python3

import fileinput
import os

Dir = input ("Source directory: ")
os.chdir(Dir)

Filelist = os.listdir()
print('File list: ',Filelist)

NomeFile = input ("Insert file name: ")

CarOr = input ("Text to search: ")

CarNew = input ("New text: ")

with fileinput.FileInput(NomeFile, inplace=True, backup='.bak') as file:
    for line in file:
        print(line.replace(CarOr, CarNew), end='')

file.close ()

1 Comment

Sad, but fileinput doen not work with inplace=True with utf-8.
1

I tried this and used readlines instead of read

with open('dummy.txt','r') as file:
    list = file.readlines()
print(f'before removal {list}')
for i in list[:]:
        list.remove(i)

print(f'After removal {list}')
with open('dummy.txt','w+') as f:
    for i in list:
        f.write(i)

Comments

1

I modified Jayram's post slightly in order to replace every instance of a '!' character to a number which I wanted to increment with each instance. I thought it might be helpful to someone who wanted to modify a character that occurred more than once per line and wanted to iterate. This worked for me.

f1 = open('file1.txt', 'r')
f2 = open('file2.txt', 'w')
n = 1

# if word=='!'replace w/ [n] & increment n; else append same word to
# file2

for line in f1:
    for word in line:
        if word == '!':
            f2.write(word.replace('!', f'[{n}]'))
            n += 1
        else:
            f2.write(word)
f1.close()
f2.close()

Comments

0

Use:

def word_replace(filename, old, new):
    c = 0
    with open(filename, 'r+', encoding ='utf-8') as f:
        a = f.read()
        b = a.split()
        for i in range(0, len(b)):
            if b[i] == old:
                c = c + 1
        old = old.center(len(old) + 2)
        new = new.center(len(new) + 2)
        d = a.replace(old, new, c)
        f.truncate(0)
        f.seek(0)
        f.write(d)

    print('All words have been replaced!!!')

1 Comment

This code will replace the word you intend. the only problem is it rewrites the whole file. might get stuck if the file is too long for the processor to handle.
0
#!/usr/bin/env python3

def find_and_replace(file_path, old_word, new_word, case_sensitive=True, create_backup=True):
    """
    Find and replace all occurrences of a word in a file.
    
    Args:
        file_path (str): Path to the file
        old_word (str): Word to find
        new_word (str): Word to replace it with
        case_sensitive (bool): Whether to perform case-sensitive replacement
        create_backup (bool): Whether to create a backup of the original file
        
    Returns:
        int: Number of replacements made
    """
    try:
        # Read the file content
        with open(file_path, 'r') as file:
            content = file.read()
        
        # Create backup if requested
        if create_backup:
            backup_path = file_path + '.bak'
            with open(backup_path, 'w') as backup_file:
                backup_file.write(content)
            print(f"Backup created at {backup_path}")
        
        # Perform the replacement
        if case_sensitive:
            new_content = content.replace(old_word, new_word)
            count = content.count(old_word)
        else:
            import re
            count = len(re.findall(re.escape(old_word), content, re.IGNORECASE))
            new_content = re.sub(re.escape(old_word), new_word, content, flags=re.IGNORECASE)
        
        # Write the modified content back to the file
        if count > 0:
            with open(file_path, 'w') as file:
                file.write(new_content)
            print(f"Successfully replaced {count} occurrence(s) of '{old_word}' with '{new_word}'.")
        else:
            print(f"No occurrences of '{old_word}' found in the file.")
        
        return count
    
    except FileNotFoundError:
        print(f"Error: File '{file_path}' not found.")
        return 0
    except Exception as e:
        print(f"Error: {str(e)}")
        return 0

def find_and_replace_in_line_range(file_path, old_word, new_word, start_line, end_line, case_sensitive=True):
    """
    Find and replace occurrences of a word within a specific range of lines.
    
    Args:
        file_path (str): Path to the file
        old_word (str): Word to find
        new_word (str): Word to replace it with
        start_line (int): Start line number (1-based index)
        end_line (int): End line number (1-based index)
        case_sensitive (bool): Whether to perform case-sensitive replacement
        
    Returns:
        int: Number of replacements made
    """
    try:
        # Read all lines from the file
        with open(file_path, 'r') as file:
            lines = file.readlines()
        
        total_lines = len(lines)
        
        # Validate line range
        if start_line < 1:
            start_line = 1
            print("Warning: Start line adjusted to 1")
            
        if end_line > total_lines:
            end_line = total_lines
            print(f"Warning: End line adjusted to {total_lines}")
            
        if start_line > end_line:
            print("Error: Start line cannot be greater than end line")
            return 0
        
        # Track replacements
        total_replacements = 0
        
        # Process each line in the range
        for i in range(start_line - 1, end_line):
            if case_sensitive:
                count = lines[i].count(old_word)
                if count > 0:
                    lines[i] = lines[i].replace(old_word, new_word)
                    total_replacements += count
            else:
                import re
                pattern = re.escape(old_word)
                count = len(re.findall(pattern, lines[i], re.IGNORECASE))
                if count > 0:
                    lines[i] = re.sub(pattern, new_word, lines[i], flags=re.IGNORECASE)
                    total_replacements += count
        
        # Write the modified content back to the file if changes were made
        if total_replacements > 0:
            with open(file_path, 'w') as file:
                file.writelines(lines)
            print(f"Successfully replaced {total_replacements} occurrence(s) of '{old_word}' with '{new_word}' between lines {start_line}-{end_line}.")
        else:
            print(f"No occurrences of '{old_word}' found between lines {start_line}-{end_line}.")
        
        return total_replacements
    
    except FileNotFoundError:
        print(f"Error: File '{file_path}' not found.")
        return 0
    except Exception as e:
        print(f"Error: {str(e)}")
        return 0


# Example usage as command line tool
if __name__ == "__main__":
    import argparse
    
    parser = argparse.ArgumentParser(description='Find and replace text in files')
    parser.add_argument('file_path', type=str, help='Path to the file')
    parser.add_argument('old_word', type=str, help='Word to find')
    parser.add_argument('new_word', type=str, help='Word to replace it with')
    parser.add_argument('--no-case-sensitive', dest='case_sensitive', action='store_false', 
                        help='Perform case-insensitive replacement')
    parser.add_argument('--no-backup', dest='create_backup', action='store_false',
                        help='Do not create backup of the original file')
    parser.add_argument('--start-line', type=int, default=None, 
                        help='Start line for replacement (use with --end-line)')
    parser.add_argument('--end-line', type=int, default=None,
                        help='End line for replacement (use with --start-line)')
    
    args = parser.parse_args()
    
    # Check if line range is specified
    if (args.start_line is not None) and (args.end_line is not None):
        find_and_replace_in_line_range(
            args.file_path, 
            args.old_word, 
            args.new_word, 
            args.start_line, 
            args.end_line, 
            args.case_sensitive
        )
    else:
        find_and_replace(
            args.file_path, 
            args.old_word, 
            args.new_word, 
            args.case_sensitive,
            args.create_backup
        )

2 Comments

While this code may answer the question, might you please edit your post to add an explanation as to why/how it works as suggested by How do I write a good answer?. This question already has several highly-upvoted answers, including the accepted answer, so adding an explanation for how your answer differs from or improves on the others would help readers to apply your answer. Thanks!
Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.
-1

I have worked this out as an exercise of a course: open file, find and replace string and write to a new file.

class Letter:

    def __init__(self):

        with open("./Input/Names/invited_names.txt", "r") as file:
            # read the list of names
            list_names = [line.rstrip() for line in file]
            with open("./Input/Letters/starting_letter.docx", "r") as f:
                # read letter
                file_source = f.read()
            for name in list_names:
                with open(f"./Output/ReadyToSend/LetterTo{name}.docx", "w") as f:
                    # replace [name] with name of the list in the file
                    replace_string = file_source.replace('[name]', name)
                    # write to a new file
                    f.write(replace_string)


brief = Letter()

Comments

-2

Like so:

def find_and_replace(file, word, replacement):
  with open(file, 'r+') as f:
    text = f.read()
    f.write(text.replace(word, replacement))

2 Comments

Please ensure that your answer improves upon other answers already present in this question.
This will append the text with replacement to the end of the file, in my opinion @Jack Aidley aswer is just what OP meant stackoverflow.com/a/17141572/6875391
-3
def findReplace(find, replace):

    import os 

    src = os.path.join(os.getcwd(), os.pardir) 

    for path, dirs, files in os.walk(os.path.abspath(src)):

        for name in files: 

            if name.endswith('.py'): 

                filepath = os.path.join(path, name)

                with open(filepath) as f: 

                    s = f.read()

                s = s.replace(find, replace) 

                with open(filepath, "w") as f:

                    f.write(s) 

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.