Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #!/usr/bin/python
- '''
- This script runs on Python 3.6+
- It replaces text in files scanning directories recursively;
- see -h for help.
- '''
- import sys, os
- from argparse import ArgumentParser
- def format_link(filepath):
- if os.path.islink(filepath):
- return f'{filepath} -> {os.path.realpath(filepath)}'
- else:
- return filepath
- def open_and_read_file(filepath, encodings):
- for e in encodings.split(','):
- try:
- file = open(filepath, 'r+', encoding=e)
- text = file.read()
- return file, text
- except UnicodeDecodeError:
- file.close()
- except Exception as e:
- print(format_link(filepath), e, sep=': ')
- break
- return None, None
- def replace_in_place(filepath, args):
- file, text = open_and_read_file(filepath, args.e)
- if file and text:
- if args.old in text:
- print(format_link(filepath))
- if args.c > 0:
- r = text.replace(args.old, args.new, args.c)
- file.seek(0)
- file.write(r)
- file.truncate()
- file.close()
- def resolve_file_path(fp, followlinks):
- if os.path.islink(fp) and not followlinks:
- return False
- if os.path.exists(fp) and os.path.isfile(fp):
- return fp
- else:
- return False
- def filter_files(filenames, dirpath, args):
- if args.ext:
- extensions = args.ext.split(',')
- filt_func = lambda fn: any(fn.lower().endswith(e) for e in extensions)
- filenames = filter(filt_func, filenames)
- for fn in filenames:
- fp = os.path.join(dirpath, fn)
- fp = resolve_file_path(fp, followlinks=args.f)
- if fp:
- real_path = os.path.realpath(fp)
- if real_path not in args.processed_files:
- args.processed_files.append(real_path)
- yield fp
- else:
- print(f"File '{real_path}' has been already processed")
- def file_list_replace(filenames, dirpath, args):
- filepaths = filter_files(filenames, dirpath, args)
- for fp in filepaths:
- replace_in_place(fp, args)
- def recursive_replace(path, args):
- walk = os.walk(path, onerror=lambda e: print(e), followlinks=args.f)
- for dirpath, dirnames, filenames in walk:
- file_list_replace(filenames, dirpath, args)
- def parse_arguments():
- desc = 'Replaces text in files scanning directories recursively. ' \
- 'Matched files names are printed to stdout.'
- epi = f'Example: {sys.argv[0]} old.txt new.txt /path/to/dir'
- arg_parser = ArgumentParser(description=desc, epilog=epi)
- sys_encoding = sys.getdefaultencoding()
- files_encoding = f'utf-8,cp1251'
- args = {
- '-c': { 'metavar': 'N',
- 'type': int,
- 'default': 1,
- 'help': 'maximum replacements count in one file; '
- '0 disables replacement, so only search will '
- 'be performed'
- },
- '-ext': { 'metavar': 'EXTENSIONS_LIST',
- 'default': '',
- 'help': 'comma separated lower case list of file extensions; '
- 'only files with extensions specified will '
- 'be processed; default is to process all files'
- },
- '-e': { 'metavar': 'ENCODINGS_LIST',
- 'default': files_encoding,
- 'help': 'comma separated list of encodings '
- f"to open files with; default is '{files_encoding}'"
- },
- '-es': { 'metavar': 'ENCODING',
- 'default': sys_encoding,
- 'help': 'encodings for `search_for.txt`; '
- f"default is '{sys_encoding}' (system)"
- },
- '-er': { 'metavar': 'ENCODING',
- 'default': sys_encoding,
- 'help': 'encodings for `replace_with.txt`; '
- f"default is '{sys_encoding}' (system)"
- },
- '-f': { 'action': 'store_true',
- 'help': 'follow symbolic links; default is not to follow '
- 'except those that explicitly passed as arguments'
- },
- 'old': { 'metavar': '<search_for.txt>',
- 'help': 'contents of this file will be searched'
- },
- 'new': { 'metavar': '<replace_with.txt>',
- 'help': 'if found in searched files, contents of '
- '`search_for.txt` will be replaced with contents '
- 'of `replace_with.txt`'
- },
- 'dirs': { 'metavar': 'DIR_OR_FILE',
- 'nargs': '+',
- 'help': 'directory or file to be processed; '
- 'directories are scanned recursively'
- }
- }
- for a in args:
- arg_parser.add_argument(a, **args[a])
- args = arg_parser.parse_args()
- return args
- def main():
- args = parse_arguments()
- try:
- args.old = open(args.old, encoding=args.es).read()
- args.new = open(args.new, encoding=args.er).read()
- except Exception as e:
- print(str(e).capitalize())
- exit(1)
- dirs = [d for d in args.dirs if os.path.isdir(d)]
- files = [f for f in args.dirs if os.path.isfile(f)]
- if not files and not dirs:
- print('Nothing to do')
- exit()
- args.processed_files = []
- for d in dirs:
- recursive_replace(d, args)
- path = ''
- args.f = True
- file_list_replace(files, path, args)
- if __name__ == '__main__':
- main()
Add Comment
Please, Sign In to add comment