Skip to content

Commit eb3910c

Browse files
find_replace_regex_colorized.py
usage: find-replace-in-files-regex.py [-h] [--dir DIR] --search-regex SEARCH_REGEX --replace-regex REPLACE_REGEX [--glob GLOB] [--dry-run] [--create-backup] [--verbose] [--print-parent-folder] [--list-non-matching] DESCRIPTION: Find and replace recursively from the given folder using regular expressions optional arguments: -h, --help show this help message and exit --dir DIR, -d DIR folder to search in; by default current folder --search-regex SEARCH_REGEX, -s SEARCH_REGEX search regex --replace-regex REPLACE_REGEX, -r REPLACE_REGEX replacement regex --glob GLOB, -g GLOB glob pattern, i.e. *.html --dry-run, -dr don't replace anything just show what is going to be done --create-backup, -b Create backup files --verbose, -v Show files which don't match the search regex --print-parent-folder, -p Show the parent info for debug --list-non-matching, -n Supress colors USAGE: find-replace-in-files-regex.py -d [my_folder] -s <search_regex> -r <replace_regex> -g [glob_pattern]
1 parent 3855ec8 commit eb3910c

1 file changed

Lines changed: 189 additions & 0 deletions

File tree

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
#!/usr/bin/env python
2+
3+
"""
4+
5+
6+
7+
Recursively find and replace text in files under a specific folder with preview of changed data in dry-run mode
8+
============
9+
10+
Example Usage
11+
---------------
12+
13+
**See what is going to change (dry run):**
14+
15+
> flip all dates from 2017-12-31 to 31-12-2017
16+
17+
find_replace.py --dir project/myfolder --search-regex "\d{4}-\d{2}-\d{2}" --replace-regex "\3-\2-\1" --dry-run
18+
19+
**Do actual replacement:**
20+
21+
find_replace.py --dir project/myfolder --search-regex "\d{4}-\d{2}-\d{2}" --replace-regex "\3-\2-\1"
22+
23+
**Do actual replacement and create backup files:**
24+
25+
find_replace.py --dir project/myfolder --search-regex "\d{4}-\d{2}-\d{2}" --replace-regex "\3-\2-\1" --create-backup
26+
27+
**Same action as previous command with short-hand syntax:**
28+
29+
find_replace.py -d project/myfolder -s "\d{4}-\d{2}-\d{2}" -r "\3-\2-\1" -b
30+
31+
Output of `find_replace.py -h`:
32+
33+
usage: find-replace-in-files-regex.py [-h] [--dir DIR] --search-regex
34+
SEARCH_REGEX --replace-regex
35+
REPLACE_REGEX [--glob GLOB] [--dry-run]
36+
[--create-backup] [--verbose]
37+
[--print-parent-folder]
38+
39+
USAGE:
40+
find-replace-in-files-regex.py -d [my_folder] -s <search_regex> -r <replace_regex> -g [glob_pattern]
41+
"""
42+
43+
from __future__ import print_function
44+
import os
45+
import fnmatch
46+
import sys
47+
import shutil
48+
import re
49+
50+
import argparse
51+
52+
53+
class Colors:
54+
Default = "\033[39m"
55+
Black = "\033[30m"
56+
Red = "\033[31m"
57+
Green = "\033[32m"
58+
Yellow = "\033[33m"
59+
Blue = "\033[34m"
60+
Magenta = "\033[35m"
61+
Cyan = "\033[36m"
62+
LightGray = "\033[37m"
63+
DarkGray = "\033[90m"
64+
LightRed = "\033[91m"
65+
LightGreen = "\033[92m"
66+
LightYellow = "\033[93m"
67+
LightBlue = "\033[94m"
68+
LightMagenta = "\033[95m"
69+
LightCyan = "\033[96m"
70+
White = "\033[97m"
71+
NoColor = "\033[0m"
72+
73+
74+
def find_replace(cfg):
75+
76+
search_pattern = re.compile(cfg.search_regex)
77+
78+
if cfg.dry_run:
79+
print('THIS IS A DRY RUN -- NO FILES WILL BE CHANGED!')
80+
81+
for path, dirs, files in os.walk(os.path.abspath(cfg.dir)):
82+
for filename in fnmatch.filter(files, cfg.glob):
83+
84+
if cfg.print_parent_folder:
85+
pardir = os.path.normpath(os.path.join(path, '..'))
86+
pardir = os.path.split(pardir)[-1]
87+
print('[%s]' % pardir)
88+
full_path = os.path.join(path, filename)
89+
90+
# backup original file
91+
if cfg.create_backup:
92+
backup_path = full_path + '.bak'
93+
94+
while os.path.exists(backup_path):
95+
backup_path += '.bak'
96+
print('DBG: creating backup', backup_path)
97+
shutil.copyfile(full_path, backup_path)
98+
99+
if os.path.islink(full_path):
100+
print("{}File {} is a symlink. Skipping{}".format(Colors.Red, full_path, Colors.NoColor))
101+
continue
102+
103+
with open(full_path) as f:
104+
old_text = f.read()
105+
106+
all_matches = search_pattern.findall(old_text)
107+
108+
if all_matches:
109+
110+
print('{}Found {} match(es) in file {}{}'.format(Colors.LightMagenta, len(all_matches), filename, Colors.NoColor))
111+
112+
new_text = search_pattern.sub(cfg.replace_regex, old_text)
113+
114+
if not cfg.dry_run:
115+
with open(full_path, "w") as f:
116+
print('DBG: replacing in file', full_path)
117+
f.write(new_text)
118+
# else:
119+
# for idx, matches in enumerate(all_matches):
120+
# print("Match #{}: {}".format(idx, matches))
121+
122+
if cfg.verbose or cfg.dry_run:
123+
colorized_old = search_pattern.sub(Colors.LightBlue + r"\g<0>" + Colors.NoColor, old_text)
124+
colorized_old = '\n'.join(['\t' + line.strip() for line in colorized_old.split('\n') if Colors.LightBlue in line])
125+
126+
colorized = search_pattern.sub(Colors.Green + cfg.replace_regex + Colors.NoColor, old_text)
127+
colorized = '\n'.join(['\t' + line.strip() for line in colorized.split('\n') if Colors.Green in line])
128+
print("{}BEFORE:{}\n{}".format(Colors.White, Colors.NoColor, colorized_old))
129+
print("{}AFTER :{}\n{}".format(Colors.Yellow, Colors.NoColor, colorized))
130+
131+
elif cfg.list_non_matching:
132+
print('File {} does not contain search regex "{}"'.format(filename, cfg.search_regex))
133+
134+
135+
if __name__ == '__main__':
136+
137+
parser = argparse.ArgumentParser(description='''DESCRIPTION:
138+
Find and replace recursively from the given folder using regular expressions''',
139+
formatter_class=argparse.RawDescriptionHelpFormatter,
140+
epilog='''USAGE:
141+
{0} -d [my_folder] -s <search_regex> -r <replace_regex> -g [glob_pattern]
142+
143+
'''.format(os.path.basename(sys.argv[0])))
144+
145+
parser.add_argument('--dir', '-d',
146+
help='folder to search in; by default current folder',
147+
default='.')
148+
149+
parser.add_argument('--search-regex', '-s',
150+
help='search regex',
151+
required=True)
152+
153+
parser.add_argument('--replace-regex', '-r',
154+
help='replacement regex',
155+
required=True)
156+
157+
parser.add_argument('--glob', '-g',
158+
help='glob pattern, i.e. *.html',
159+
default="*.*")
160+
161+
parser.add_argument('--dry-run', '-dr',
162+
action='store_true',
163+
help="don't replace anything just show what is going to be done",
164+
default=False)
165+
166+
parser.add_argument('--create-backup', '-b',
167+
action='store_true',
168+
help='Create backup files',
169+
default=False)
170+
171+
parser.add_argument('--verbose', '-v',
172+
action='store_true',
173+
help="Show files which don't match the search regex",
174+
default=False)
175+
176+
parser.add_argument('--print-parent-folder', '-p',
177+
action='store_true',
178+
help="Show the parent info for debug",
179+
default=False)
180+
181+
parser.add_argument('--list-non-matching', '-n',
182+
action='store_true',
183+
help="Supress colors",
184+
default=False)
185+
186+
config = parser.parse_args(sys.argv[1:])
187+
188+
find_replace(config)
189+

0 commit comments

Comments
 (0)