Skip to content

Instantly share code, notes, and snippets.

@ericvoid
Last active September 14, 2020 21:18
Show Gist options
  • Save ericvoid/f0aefe1319fec027b63aad50c41494be to your computer and use it in GitHub Desktop.
Save ericvoid/f0aefe1319fec027b63aad50c41494be to your computer and use it in GitHub Desktop.
replace.py - A tool to find-and-replace text in files using regex

The most simple usage is as follows:

$ replace.py 'foobarz' 'foo bar' myfile.txt

The tool searches for foobarz on the contents of myfile.txt and replaces it with foo bar. The result is written on stdout. This is useful to check if the script is doing what you expect.

If you want the tool to change de file itself, use the -I argument (use backups or versioning to avoid losing data):

$ replace.py -I 'foobarz' 'foo bar' myfile.txt

To replace on multiple files recursively use find together:

$ find . -name *.txt -exec replace.py -I 'foobarz' 'foo bar' {} \;

This tool was made specially to do replacements with regex. The script actually uses Python's re.sub to perform the replacements. Refer to the official docs for details.

$ find . -name '*.py' -exec replace.py -I '(\W)(f|foo|foobar)\.tostr\(\)' '\1str(\2)' {} \;

The example above searches for all Python files recursively and replaces all calls to f.tostr(), foo.tostr() and foobar.tostr() with str(f), str(foo) or str(foobar).

Read the tool's help text for the available arguments to control the re.sub behavior.

#!/usr/bin/python3
import re
import sys
from difflib import unified_diff
if __name__ == '__main__':
import argparse
parser = argparse.ArgumentParser(
description="Script to find-and-replace text in files using regex. "
"The default behavior is to print to stdout the unified diff of the changes "
"(when there are no changes nothing is printed). If you want to write all "
"the contents to stdout, then use the -I argument. Or if you want to do "
"inplace replacements use the -I argument.",
epilog="For more informations refer to Python documentation on Regular Expressions. "
"This script uses 're.sub' function to perform the substitution.",
)
parser.add_argument("-c", "--count", type=int,
help="the maximum number of replacements to perform "
"(all occurrences are replaced by default)",
)
parser.add_argument("-m", "--multiline", action='store_true',
help="makes '^' also match the beginning of lines and '$' match the end of lines "
"(both of them doesn't include the newline character)",
)
parser.add_argument("-i", "--ignorecase", action='store_true',
help="ignore case when performing the search",
)
parser.add_argument("-s", "--dotall", action='store_true',
help="make the '.' special char match any character including newlines",
)
parser.add_argument("-I", "--inplace", action='store_true',
help="use this flag to do an in-place substitution",
)
parser.add_argument("-e", "--encoding", type=str, default="utf-8",
help="the encoding to use when reading and writing files",
)
parser.add_argument("-p", "--print", action='store_true',
help="prints to stdout all the file contents after replacing all the occurrences "
"(even when no changes ocurred)",
)
parser.add_argument("search",
help="the regex pattern to search",
)
parser.add_argument("replace",
help="the replacement text",
)
parser.add_argument("path",
help="the path to the file to perform the substitution",
)
args = parser.parse_args()
if not args.search or not args.replace or not args.path:
parser.print_help(sys.stderr)
print('search, replace and path arguments should be passed')
sys.exit(1)
# SETUP re.sub KEYWORD ARGUMENTS
sub_kwargs = {'flags': 0}
if args.count:
sub_kwargs['count'] = args.count
if args.multiline:
sub_kwargs['flags'] |= re.MULTILINE
if args.ignorecase:
sub_kwargs['flags'] |= re.IGNORECASE
if args.dotall:
sub_kwargs['flags'] |= re.DOTALL
# EXECUTES THE REPLACEMENT
with open(args.path, encoding=args.encoding, mode='r') as f:
data = f.read()
result = re.sub(args.search, args.replace, data, **sub_kwargs)
if args.print:
print(result)
elif args.inplace:
if result != data:
with open(args.path, encoding=args.encoding, mode='w') as f:
f.write(result)
else:
if result != data:
sys.stdout.writelines(unified_diff(data.splitlines(True), result.splitlines(True), 'before/'+args.path, 'after/'+args.path))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment