Last active
December 6, 2022 18:02
-
-
Save glowinthedark/b0186679a2086675c6a099e4cdd6a0d1 to your computer and use it in GitHub Desktop.
replace files using regular expressions
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
# -*- coding: utf-8 -*- | |
# NOTES | |
# - to search recursively in subfolders pass `--glob "**/*.*"` | |
import argparse | |
import re | |
import sys | |
from pathlib import Path | |
RED = "\033[31m" | |
GREEN = "\033[32m" | |
NOCOLOR = "\033[0m" | |
def rename_files(args): | |
if not args.search and (args.lowercase or args.uppercase): | |
args.search = '(^.*$)' | |
args.replace = '\\1' | |
root: Path = Path(args.root_folder) | |
for filepath in root.glob(args.glob): | |
colorized_old_filename = None | |
colorized_new_filename = None | |
new_filename = None | |
filename = filepath.name | |
if args.literal and args.search in filename: | |
# literal replace | |
new_filename = filename.replace(args.search, args.replace) | |
colorized_old_filename = filename.replace(args.search, f'{RED}{args.search}{NOCOLOR}') | |
colorized_new_filename = filename.replace(args.search, f'{GREEN}{args.replace}{NOCOLOR}') | |
elif not args.literal and re.compile(args.search).findall(filename): | |
new_filename = re.compile(args.search).sub(args.replace, filename) | |
colorized_old_filename = re.sub(bracketize_regex(args.search), f'{RED}\\1{NOCOLOR}', filename) | |
colorized_new_filename = re.compile(args.search).sub(f'{GREEN}{args.replace}{NOCOLOR}', filename) | |
# to lowercase only | |
if args.lowercase and new_filename: | |
new_filename = filename.lower() | |
colorized_new_filename = colorized_new_filename.lower() | |
# to UPPERCASE only | |
if args.uppercase and new_filename: | |
new_filename = filename.upper() | |
colorized_new_filename = colorized_new_filename.upper() | |
if not new_filename: | |
continue | |
# filepath_old = filename | |
# filepath_new = new_filename | |
# filepath_old = os.path.join(path, filename) | |
# filepath_new = os.path.join(path, new_filename) | |
# if args.lowercase: | |
# new_filename = new_filename.lower() | |
# if args.uppercase: | |
# new_filename = new_filename.upper() | |
if not new_filename: | |
print('Replacement expression "{}"" returns empty value! Skipping'.format(args.replace)) | |
continue | |
else: | |
print(colorized_old_filename, '→', colorized_new_filename) | |
if args.write_changes: | |
new_path_abs = Path(filepath.parent.absolute()/new_filename) | |
if not new_path_abs.exists() or args.overwrite_existing: | |
filepath.rename(new_path_abs) | |
# shutil.move(filepath.absolute(), new_path_abs) | |
else: | |
print(f"WARNING: Target file already exists: {new_filename} - Refusing to overwrite.") | |
else: | |
if args.show_non_matching: | |
print('Name [{filename}] does not match search regex [{args.search}]') | |
def bracketize_regex(rx): | |
"""surround regex with brackets if needed""" | |
if rx.startswith('(') and rx.endswith(')'): | |
return rx | |
else: | |
return f'({rx})' | |
def is_required_arg(argz): | |
for key in ('-k', '-K', '--lowercase', '--uppercase'): | |
if key in argz: | |
return False | |
return True | |
if __name__ == '__main__': | |
parser = argparse.ArgumentParser(description='Recursive file name renaming with regex support') | |
parser.add_argument('root_folder', | |
help='Top folder for the replacement operation', | |
nargs='?', | |
action='store', | |
default='.') | |
parser.add_argument('-s', '--search', | |
help='string to search for', | |
action='store', | |
default=None, | |
required=is_required_arg(sys.argv)) | |
parser.add_argument('-r', '--replace', | |
help='string to replace with', | |
action='store', | |
default=None, | |
required=is_required_arg(sys.argv)) | |
parser.add_argument('-w', '--write-changes', | |
action='store_true', | |
help='Write changes to files (otherwise just simulate the operation) (DEFAULT: False)', | |
default=False) | |
parser.add_argument('-l', '--literal', | |
action='store_true', | |
help='Use literal text for search/replace (not regular expressions) (DEFAULT: False)', | |
default=False) | |
parser.add_argument('-y', '--overwrite_existing', | |
action='store_true', | |
help='Overwrite existing files', | |
default=False) | |
parser.add_argument('-g', '--glob', | |
action='store', | |
help='only process files matching glob (DEFAULT: "*.*")', | |
default="*.*") | |
parser.add_argument('-v', '--show-non-matching', | |
action='store_true', | |
help='Show files that do not match (DEFAULT: False)', | |
default=False) | |
parser.add_argument('-k', '--lowercase', | |
action='store_true', | |
help='Convert to lower case (DEFAULT: False)', | |
default=False) | |
parser.add_argument('-K', '--uppercase', | |
action='store_true', | |
help='Convert to UPPER CASE (DEFAULT: False)', | |
default=False) | |
args = parser.parse_args(sys.argv[1:]) | |
print(args) | |
rename_files(args) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment