RENAME - PATTERN CAPTURE MAGIC
This is where rename gets seriously powerful. Capture groups let you rearrange, transform, and restructure filenames.
Capture Groups Explained
Parentheses in regex capture what they match:
Pattern: (\d+)
Input: photo_123.jpg
Capture: $1 = "123"
You can have multiple captures:
Pattern: (\w+)_(\d+)\.(\w+)
Input: photo_123.jpg
Captures: $1 = "photo", $2 = "123", $3 = "jpg"
In the replacement, use $1, $2, $3, etc.
Rearranging Parts
Swap the order of filename parts:
rename 's/(\w+)_(\d+)/$2_$1/' *.jpg
Before: photo_001.jpg
After: 001_photo.jpg
This captures "photo" as $1 and "001" as $2, then puts them back in reverse order.
Restructuring Dates
Convert MMDDYYYY to YYYY-MM-DD:
rename 's/(\d{2})(\d{2})(\d{4})/$3-$1-$2/' *
Before: 01152024_report.txt
After: 2024-01-15_report.txt
Each \d{2} or \d{4} captures its digits into numbered groups.
Padding Numbers
Turn "file1.txt" into "file001.txt":
rename 's/(\d+)/sprintf("%03d", $1)/e' *
Before: file1.txt, file2.txt, file10.txt
After: file001.txt, file002.txt, file010.txt
The /e flag evaluates Perl code. sprintf formats the number.
Breaking it down:
- (\d+) captures any number
- sprintf("%03d", $1) formats it with leading zeros
- /e makes Perl execute the replacement as code
Extracting and Keeping Parts
Keep only the number:
rename 's/.*_(\d+)\.(.+)/$1.$2/' *
Before: random_garbage_text_042.jpg
After: 042.jpg
The .* eats everything up to the last underscore before the number.
Working with Camera Files
Camera files often look like: IMG_20240115_143022.jpg (That's IMG_YYYYMMDD_HHMMSS)
Make them human-readable:
rename 's/IMG_(\d{4})(\d{2})(\d{2})_(\d{2})(\d{2})(\d{2})/$1-$2-$3_$4-$5-$6/' IMG_*
Before: IMG_20240115_143022.jpg
After: 2024-01-15_14-30-22.jpg
Non-Capturing Groups
Sometimes you need to group but not capture. Use (?:...)
rename 's/(?:IMG_|DSC_)(\d+)/$1/' *
This matches either IMG_ or DSC_ but doesn't capture it. Only the number goes into $1.
Conditional Replacement
The /e flag lets you use Perl logic:
rename 's/(\d+)/($1 < 100 ? sprintf("0%02d", $1) : $1)/e' *
This pads numbers under 100 differently. Probably overkill, but shows what's possible.
Practical Example: AI-Generated Images
Stable Diffusion often outputs files like:
00001-1234567890-masterpiece_best_quality.png
Let's clean these up:
Step 1 - See what we have (dry run):
rename -n 's/^(\d+)-(\d+)-(.+)\.png$/$1_$3.png/' *.png
Step 2 - Simplify the prompt part:
rename -n 's/_/ /g' * # underscores to spaces
rename -n 's/\s+/_/g' * # collapse spaces to single underscore
Step 3 - Final format:
rename 's/^(\d+)-\d+-(.+)\.png$/image_$1.png/' *.png
Practical Example: Batch Rename Screenshots
Mac screenshots: "Screenshot 2024-01-15 at 2.30.22 PM.png"
Clean format: "2024-01-15-1430.png"
rename 's/Screenshot (\d{4})-(\d{2})-(\d{2}) at (\d+)\.(\d+)\.(\d+) (AM|PM)\.png/$1-$2-$3-$4$5.png/' Screenshot*
This is complex! Let's break it down:
Screenshot Fixed text
(\d{4}) Year -> $1
-(\d{2}) Month -> $2
-(\d{2}) Day -> $3
at Fixed text
(\d+) Hour -> $4
\.(\d+) Minute -> $5
\.(\d+) Second -> $6
(AM|PM) AM/PM -> $7
\.png Extension
Result: YYYY-MM-DD-HHMM.png
Practical Example: Organizing by Date
Move files into date folders:
# This requires mkdir + mv, not just rename
for f in 2024*.txt; do
dir=$(echo "$f" | sed 's/^\([0-9-]*\)_.*/\1/')
mkdir -p "$dir"
mv "$f" "$dir/"
done
Rename can't create directories, so sometimes you need a loop.
Handling Edge Cases
What if filenames have weird characters?
Use \Q...\E to quote metacharacters:
rename 's/\Q[1]\E/_v1/' *
Before: file[1].txt
After: file_v1.txt
\Q and \E turn off regex interpretation between them.
Chaining Operations
Complex renames are easier as multiple steps:
# Step 1: Normalize case
rename 'y/A-Z/a-z/' *
# Step 2: Replace spaces
rename 's/ /_/g' *
# Step 3: Remove special characters
rename 's/[^a-z0-9_.-]//gi' *
# Step 4: Collapse multiple underscores
rename 's/_+/_/g' *
Each step is simple. Together they completely sanitize filenames.
Debugging Complex Patterns
When patterns don't work:
1. Test the regex on sample text first (use Perl or regex101.com) 2. Use -n to see what would happen 3. Add -v for verbose output 4. Simplify - try matching just part of the filename first 5. Build up gradually, adding capture groups one at a time