I have some scripts I use a lot when renaming large numbers of files. One does simple text search and replace, the other uses Python's regular expressions. (A note on style: when writing short scripts for me, which largely fit on a screen or two, I tend to use short variable names. I have a maths background, and so 'let g be a group and let x∈g' type language is natural to me, and this extends to coding. If writing something many pages long, and possibly spread over multiple files, then as is sensible, I start using descriptive variable names.) ## Examples ```bash # replace all spaces with underscores filesr " " "_" * # replace all runs of weird characters with an underscore filesrx '[^a-zA-Z0-9_\.-]+' '_' * # replace all instances of 'turnip' or 'tomato' with vegetable filesrx '(turnip|tomato)' 'vegetable' # for all files with runs of 3 digits in their filename, rename to e.g. file_4562.xyz filesrx '^.*(\d+).*' 'file_\1.xyz' # Apply tr to filename # change all x's to y's and all a's to b's filetr xy ab ... ``` ## Code These scripts use the `filesr_common.py` module at the bottom. ### filesr ```py #!/usr/bin/env python3 import sys,os,re import uuid,os.path from filesr_common import Renamer args = sys.argv[1:] overwrite = False verbose = False if os.getenv("OVERWRITE") in ["y","Y"]: overwrite = True if os.getenv("VERBOSE") in ["y","Y"]: verbose = True forreal = True if os.getenv("DRYRUN") is not None and os.getenv("DRYRUN") not in ["n","N"]: forreal = False elif os.getenv("D") is not None and os.getenv("D") not in ["n","N"]: forreal = False if len(args) < 3: print("filesr searchpat replpat ") if len(args) == 2: print("(did you forget to specify files)") sys.exit(1) s,r = tuple(args[:2]) filenames = args[2:] print(f"""filesr search: {s} replace with: {r}""") renner = Renamer(forreal=forreal,overwrite=overwrite,verbose=verbose) renner.sr(s,r) renner.process(filenames) ``` ### filesrd ```py #!/usr/bin/env python3 import sys,os import uuid,os.path args = sys.argv[1:] overwrite = False if os.getenv("OVERWRITE") in ["y","Y"]: overwrite = True forreal = True if os.getenv("DRYRUN") is not None and os.getenv("DRYRUN") not in ["n","N"]: forreal = False elif os.getenv("D") is not None and os.getenv("D") not in ["n","N"]: forreal = False if len(args) < 2: print("filesrd chars_to_delete ") sys.exit(1) chars_to_delete = args[0] files = args[1:] print(f"""filesr chars_to_delete: {chars_to_delete}""") def rn(x,y): if forreal: if os.path.exists(y): print(f"{y} exists") if overwrite: try: print(f"Overwritten {y}") os.rename(x,y) except IsADirectoryError: print(f"{y} is a directory") else: os.rename(x,y) while True: t = str(uuid.uuid4()) if not os.path.exists(t): break print("Temp name {}".format(t)) for x in files: y = x for c in chars_to_delete: y = y.replace(c,"") if x == y: pass elif os.path.exists(y): print("{} already exists".format(y)) else: xa = x.lower() ya = y.lower() if xa == ya: print("Capitalisation issue") print(f"Using temp name {t}: {x} --> {y}") rn(x,t) rn(t,y) else: print(f"Rename {x} --> {y}") rn(x,y) ``` ### filesrx ```py #!/usr/bin/env python3 import sys,os,re import uuid,os.path from filesr_common import Renamer args = sys.argv[1:] overwrite = False verbose = False if os.getenv("OVERWRITE") in ["y","Y"]: overwrite = True if os.getenv("VERBOSE") in ["y","Y"]: verbose = True forreal = True if os.getenv("DRYRUN") is not None and os.getenv("DRYRUN") not in ["n","N"]: forreal = False elif os.getenv("D") is not None and os.getenv("D") not in ["n","N"]: forreal = False if len(args) < 3: print("filesrx searchpat replpat ") print(" searchpad is regex") if len(args) == 2: print("(did you forget to specify files)") sys.exit(1) s,r = tuple(args[:2]) filenames = args[2:] print(f"""filesrx regex: {s} replace with: {r}""") renner = Renamer(forreal=forreal,overwrite=overwrite,verbose=verbose) renner.srx(s,r) renner.process(filenames) ``` ### filesrxd ```py #!/bin/bash export DRYRUN=y filesrx "$@" ``` ### filesr_common.py ```py #!/usr/bin/env python3 import sys,os,re import uuid,os.path from icecream import ic; ic.configureOutput(includeContext=True) class Renamer: def __init__(self,forreal = False, overwrite = False, verbose = False): self.renner = None self.forreal = forreal self.verbose = verbose self.overwrite = overwrite def sr(self,search,replacement): def filesr(filename): return filename.replace(search,replacement) self.renner = filesr def srx(self,search,replacement): regex = re.compile(search) def filesr(filename): try: m = regex.search(filename) if m is None and self.verbose: print(f"File {filename} does not match {s}") return regex.sub(replacement,filename) except Exception as e: ic(f"Regex {search} failed for file {filename}",e) return None self.renner = filesr def tr(self,search,replacement): trans = str.maketrans(search,replacement) def filesr(filename): return filename.translate(trans) self.renner = filesr def process(self,filenames): while True: t = str(uuid.uuid4()) if not os.path.exists(t): break print("Temp name {}".format(t)) temp_name = t for x in filenames: y = self.renner(x) if y is None: print(f"replacment func failed for {x}") continue if x == y: pass elif os.path.exists(y): sx = os.stat(x) sy = os.stat(y) if not sx.st_ino == sy.st_ino: print("{} already exists".format(y)) else: # x and y are the same file print("Capitalisation issue") print(f"Using temp name {t}: {x} --> {y}") self.rn(x,t) self.rn(t,y) else: print(f"Rename {x} --> {y}") self.rn(x,y) def rn(self,x,y): if x == y: print(f"No change in {x}") return if self.forreal: if os.path.exists(y): print(f"{y} exists") if self.overwrite: try: print(f"Overwritten {y}") os.rename(x,y) except IsADirectoryError: print(f"{y} is a directory") else: os.rename(x,y) ```