40

I'm after a way to batch rename files with a regex i.e.

s/123/onetwothree/g

I recall i can use awk and sed with a regex but couldnt figure out how to pipe them together for the desired output.

Fred Foo
  • 355,277
  • 75
  • 744
  • 836
user370507
  • 477
  • 2
  • 12
  • 20
  • I just wanted to thank ccpizza as the Python regex script worked great for me. I would have upvoted but I'm a noob so can't... can't do much... can't even comment. I used his Python script on my MacBook Pro to rename a load of files with capture groups, so I didn't loose any vital information and I was able to shift the filenames into a more acceptable format. I'm only just starting out with Python but love regex and this script worked great for me! – NutSoft Feb 07 '17 at 13:44

5 Answers5

118

You can install perl based rename utility:

brew install rename

and than just use it like:

rename 's/123/onetwothree/g' *

if you'd like to test your regex without renaming any files just add -n switch

Akseli Palén
  • 27,244
  • 10
  • 65
  • 75
Viperet
  • 1,355
  • 2
  • 8
  • 9
30

An efficient way to perform the rename operation is to construct the rename commands in a sed pipeline and feed them into the shell.

ls |
sed -n 's/\(.*\)\(123\)\(.*\)/mv "\1\2\3" "\1onetwothree\2"/p' |
sh
Diomidis Spinellis
  • 18,734
  • 5
  • 61
  • 83
  • 1
    The \2 after onetwothree should be \3 – George Dec 15 '14 at 09:09
  • 2
    The only thing that worked recursively (with using the find command) `$ find . -iname test* | sed -E 's/\.\/(test)(.*)(\.jpg)/mv \1\2\3 \1\214\3/' | sh` – Binarian Aug 28 '15 at 09:01
12

Namechanger is super nice. It supports regular expressions for search and replace: consider that I am doing a super complex rename with the following regex: \.sync-conflict-.*\. thats a life saver.

Regex captures groups (Diomidis answer) be the CLI way, into variables I think called $1 and $2 so rename -nv 's/^(\d{2})\.(\d{2}).*/s$1e$2.mp4/' *.mp4 becomes possible. Notice the $1 and $2? Those are coming from capture group one (\d{2}) and two (\d{2}) in my example.

enter image description here

Tomachi
  • 1,665
  • 1
  • 13
  • 15
  • 1
    Yeah.. question does say Terminal... useful nonetheless! https://mrrsoftware.com/namechanger/ – ptim Dec 26 '18 at 23:23
  • \.sync-conflict-.*\. is all im gonna say to that. perhaps with ls xargs and mv i could do similar. this app is one of those ones that makes the entire operating system worth running. a true beauty of a program. – Tomachi Jul 23 '19 at 19:52
  • Namechanger is a game changer! Thanks so much for the pointer. It works under MacOS 12.6 on an M2 Mac, but could use a recompilation. I'll try to contact the author. – Adam Wildavsky Oct 03 '22 at 19:17
8

My take on a friendly recursive regex file name renamer which by default only emulates the replacement and shows what the resulting file names would be.

Use -w to actually write changes when you are satisfied with the dry run result, -s to suppress displaying non-matching files; -h or --help will show usage notes.

Simplest usage:

# replace all occurences of 'foo' with 'bar'
# "foo-foo.txt" >> "bar-bar.txt"
ren.py . 'foo' 'bar' -s

# only replace 'foo' at the beginning of the filename
# "foo-foo.txt" >> "bar-foo.txt"
ren.py . '^foo' 'bar' -s

Matching groups (e.g. \1, \2 etc) are supported too:

# rename "spam.txt" to "spam-spam-spam.py"
ren.py . '(.+)\.txt' '\1-\1-\1.py' -s 

# rename "12-lovely-spam.txt" to "lovely-spam-12.txt"
# (assuming two digits at the beginning and a 3 character extension 
ren.py . '^(\d{2})-(.+)\.(.{3})' '\2-\1.\3' -s

NOTE: don't forget to add -w when you tested the results and want to actually write the changes.

Works both with Python 2.x and Python 3.x.

#!/usr/bin/python
# -*- coding: utf-8 -*-
from __future__ import print_function
import argparse
import os
import fnmatch
import sys
import shutil
import re


def rename_files(args):

    pattern_old = re.compile(args.search_for)

    for path, dirs, files in os.walk(os.path.abspath(args.root_folder)):

        for filename in fnmatch.filter(files, "*.*"):

            if pattern_old.findall(filename):
                new_name = pattern_old.sub(args.replace_with, filename)

                filepath_old = os.path.join(path, filename)
                filepath_new = os.path.join(path, new_name)

                if not new_name:
                    print('Replacement regex {} returns empty value! Skipping'.format(args.replace_with))
                    continue

                print(new_name)

                if args.write_changes:
                    shutil.move(filepath_old, filepath_new)
            else:
                if not args.suppress_non_matching:
                    print('Name [{}] does not match search regex [{}]'.format(filename, args.search_for))


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('search_for',
                        help='string to search for',
                        action='store')
    parser.add_argument('replace_with',
                        help='string to replace with',
                        action='store')
    parser.add_argument('-w', '--write-changes',
                        action='store_true',
                        help='Write changes to files (otherwise just simulate the operation)',
                        default=False)
    parser.add_argument('-s', '--suppress-non-matching',
                        action='store_true',
                        help='Hide files that do not match',
                        default=False)

    args = parser.parse_args(sys.argv[1:])

    print(args)
    rename_files(args)
ccpizza
  • 28,968
  • 18
  • 162
  • 169
6
files = "*"
for f in $files; do
     newname=`echo "$f" | sed 's/123/onetwothree/g'`
     mv "$f" "$newname"
done
divieira
  • 885
  • 9
  • 17
Fred Foo
  • 355,277
  • 75
  • 744
  • 836