Skip to content

Instantly share code, notes, and snippets.

@pmav99
Last active January 10, 2017 12:49
Show Gist options
  • Save pmav99/1fc8d7e3e37ab12743f1581554ab684d to your computer and use it in GitHub Desktop.
Save pmav99/1fc8d7e3e37ab12743f1581554ab684d to your computer and use it in GitHub Desktop.
Pure Python 3 search and replace

This script is similar to:

ag pattern -l | xargs sed -i 's;pattern;replace;g'

with the added benefit that you can use proper regular expressions (e.g. grouping, lookaheads etc which are not supported on sed...).

Since ag is being used for searching the files, performance shouldn't be too bad (especially on SSDs).

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# module: sr.py
# licence: BSD
# author: Panagiotis Mavrogiorgos - pmav99 google mail
"""
usage: sr.py [-h] [-s SEARCH] regex replace [search_args]
A pure python equivalent to:
'ag <regex> -l <search_args> | xargs sed -i 's/<regex>/<replace>/g'
positional arguments:
regex The regex pattern we want to match.
replace The replace text that we want to use.
search_args Any additional arguments that we want to pass to the search
program.
optional arguments:
-h, --help show this help message and exit
-s , --search The executable that we want to use in order to search for
matches. Defaults to 'ag'.
"""
import re
import sys
import shutil
import argparse
import warnings
import subprocess
class MyParser(argparse.ArgumentParser):
def error(self, message):
sys.stderr.write('error: %s\n' % message)
self.print_help()
sys.exit(2)
def main(args):
regex = args.regex
replace = args.replace
search = args.search
search_args = args.search_args
# Check if ag is available
if shutil.which(search) is None:
sys.exit("Coulnd't find <%s>. Please install it and try again." % search)
# We DO need "-l" when we use ag!
if search == "ag" and "-l" not in search_args:
search_args.append("-l")
cmd = [search, *search_args, regex]
try:
output = subprocess.check_output(cmd)
except subprocess.CalledProcessError:
sys.exit("Couldn't find any matches. Check your regex")
filenames = output.decode("utf-8").splitlines()
for filename in filenames:
# print(filename)
# open file
with open(filename) as fd:
original = fd.read()
# replace text
try:
substituted = re.sub(regex, replace, original)
except Exception:
sys.exit("The regex is invalid: %r" % regex)
if original == substituted:
warnings.warn("Warning: no substitutions made: %s" % filename)
else:
# write file inplace
with open(filename, "w") as fd:
fd.write(substituted)
if __name__ == "__main__":
# parser = argparse.ArgumentParser(
parser = MyParser(
description="A pure python equivalent to:\n\n\t'ag <regex> -l <search_args> | xargs sed -i 's/<regex>/<replace>/g'",
usage="sr.py [-h] [-s SEARCH] regex replace [search_args]",
formatter_class=argparse.RawDescriptionHelpFormatter,
)
parser.add_argument("regex", help="The regex pattern we want to match.")
parser.add_argument("replace", help="The replace text that we want to use.")
parser.add_argument("-s", "--search", help="The executable that we want to use in order to search for matches. Defaults to 'ag'.", default="ag", metavar='')
parser.add_argument("search_args", help="Any additional arguments that we want to pass to the search program.", nargs=argparse.REMAINDER)
args = parser.parse_args()
main(args)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment