DocsCommands
shoots rename
Batch-rename already-imported files in place, using the same templating engine as import.
shoots rename <path> --pattern <template> [options]
Arguments
| Argument | Required | Description |
|---|---|---|
<path> |
yes | Folder (or single file) to rename in place |
Options
| Option | Default | Description |
|---|---|---|
--pattern <template> |
required | Filename template, e.g. "{date}_{time}_{camera}_{seq:4}.{ext}" |
--recursive |
off | Recurse into subdirectories (each file stays in its own directory) |
--dry-run |
off | Show planned renames without executing them |
--json |
off | Machine-readable JSON on stdout |
--verbose |
off | Verbose logging on stderr |
Note the default. Unlike most commands,
renameis non-recursive by default. Renaming is destructive-ish enough that recursing into an entire catalog should be an explicit choice.
Behaviour
Two-phase renaming makes swaps safe
Renames execute in two passes:
- every changing file → a unique temp name in its own directory,
- temp name → final name.
This makes in-set collisions and swaps (A → B, B → A) safe — impossible with a
naive single-pass rename. If phase 2 fails for a file, it is rolled back to its
original name rather than left stranded at a temp name.
Files never leave their directory
rename is an in-place operation. With --recursive, each file is renamed within
the directory it already lives in; the folder structure is untouched. To move
files, use import.
Unchanged files are counted, not touched
A file whose rendered name equals its current name is reported as unchanged and
skipped entirely.
exiftool is required
Unlike import, rename does not degrade to mtime. Templates here are
expected to resolve real capture metadata, so exiftool must be available — it is
provisioned on first use, or you can point SHOOTS_EXIFTOOL at an existing binary.
Examples
Preview first, always
shoots rename D:/Shoots/2026/smith-wedding/raw \
--pattern "{date}_{seq:4}_{orig}.{ext}" --dry-run
rename IMG_0001.CR3 → 20260719_0001_IMG_0001.cr3
rename IMG_0002.CR3 → 20260719_0002_IMG_0002.cr3
(dry run) 482 files would be renamed, 0 unchanged
Execute
shoots rename D:/Shoots/2026/smith-wedding/raw --pattern "{date}_{seq:4}_{orig}.{ext}"
ok IMG_0001.CR3 → 20260719_0001_IMG_0001.cr3
ok IMG_0002.CR3 → 20260719_0002_IMG_0002.cr3
482 renamed, 0 unchanged, 0 failed
Normalize a whole catalog
shoots rename D:/Shoots/2026 \
--pattern "{year}{month}{day}_{time}_{camera}_{seq:4}.{ext}" \
--recursive --dry-run
Each dated subfolder keeps its files; only names change.
Client-facing names
shoots rename ./selects --pattern "SmithWedding_{seq:3}.{ext}"
→ SmithWedding_001.cr3, SmithWedding_002.cr3, …
Undo-friendly workflow
rename has no undo. Capture the mapping first:
shoots rename ./raw --pattern "{date}_{seq:4}.{ext}" --dry-run --json > rename-plan.json
shoots rename ./raw --pattern "{date}_{seq:4}.{ext}" --json > rename-done.json
rename-done.json carries every source → dest pair, which is enough to script a
reversal.
JSON output
Dry run
{
"command": "rename",
"dryRun": true,
"files": [
{ "source": "D:\\...\\IMG_0001.CR3", "dest": "D:\\...\\20260719_0001.cr3", "unchanged": false },
{ "source": "D:\\...\\20260719_0002.cr3", "dest": "D:\\...\\20260719_0002.cr3", "unchanged": true }
]
}
Real run
{
"command": "rename",
"dryRun": false,
"files": [
{ "source": "D:\\...\\IMG_0001.CR3", "dest": "D:\\...\\20260719_0001.cr3" }
],
"errors": [],
"summary": { "total": 482, "renamed": 482, "unchanged": 0, "failed": 0 }
}
Only files that actually changed appear in files on a real run.
Exit codes
| Code | When |
|---|---|
0 |
All renames succeeded (or nothing needed renaming) |
1 |
At least one rename failed |
2 |
Invalid --pattern, or exiftool unavailable |
See also
- Filename templates — every token, with worked examples
import— the same engine, applied while copying