lang/python/ PowerRename


I have two 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

filesr " " "_" *                       # replace all spaces with underscores
filesrx '[^a-zA-Z0-9_\.-]+' '_' *      # replace all runs of weird characters with an underscore
filesrx '(turnip|tomato)' 'vegetable'  # replace all instances of 'turnip' or 'tomato' with vegetable
filesrx '^.*(\d+).*' 'file_\1.xyz'     # for all files with runs of 3 digits in their filename, rename to e.g. file_4562.xyz

Code

filesr — filename search and replace

#!/usr/bin/env python3

import sys,os
import uuid,os.path

args = sys.argv[1:]

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 <files>")
    sys.exit(1)

s,r = tuple(args[:2])
files = args[2:]
print(f"""filesr
      search: {s}
replace with: {r}""")

def rn(x,y):
    if forreal:
        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.replace(s,r)
    xa = x.lower()
    ya = y.lower()
    if x == y:
        pass
    elif os.path.exists(y) and not xa == ya:
        print("{} already exists".format(y))
    else:
        if xa == ya:
            print("Capitalisation issue") # for dealing with filesystems that are not case sensitive
            print(f"Using temp name {t}: {x} --> {y}")
            rn(x,t)
            rn(t,y)
        else:
            print(f"Rename {x} --> {y}")
            rn(x,y)

filesrx — filename regex search and replace

#!/usr/bin/env python3

import sys,os,re
import uuid,os.path

args = sys.argv[1:]
dryrun = os.getenv("D","n").lower() == "y"
case_insensitive = os.getenv("I","n").lower() == "y"

def print_usage():
  print(f"""{sys.argv[0].split('/')[-1]} <search regex> <replacement> [files...] # file regex search and replace
set env variables I=y to set case insensiive, D=y to enable dry run
""")

def rn(x,y):
  if not dryrun:
    os.rename(x,y)

def main():
  if len(args) < 3:
    if len(args) == 2:
      print(f"No files specified")
    print_usage()
    exit(1)
  try:
    s,r = tuple(args[:2])
    files = args[2:]
  except Exception:
    print_usage()
    exit(1)

  print(f"""{sys.argv[0]} # file regex search and replace
           regex: {s}
    replace with: {r}
         dry run: {dryrun}
case insensitive: {case_insensitive}
""")

  if case_insensitive:
    sr = re.compile(s,re.I)
  else:
    sr = re.compile(s)

  while True:
    t = str(uuid.uuid4())
    if not os.path.exists(t):
      break
  print("Temp name {}".format(t))

  for x in files:
    try:
      y = sr.sub(r,x)
    except:
      print(f"Regex {s} failed for file {x}")
      continue
    if x != y:
      print(f"{x} => {y}")
      xa = x.lower()
      ya = y.lower()
      if x == y:
        pass
      elif os.path.exists(y) and not xa == ya:
        print(f"{y} already exists")
      else:
        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)

if __name__ == "__main__":
  main()