batch-namer: Simple, Dependency-Free File Renaming
I built batch-namer to solve a common problem: renaming large collections of files without installing heavy dependencies. This lightweight Python script uses only standard library modules to rename files with patterns like "IMG_2024-", UUIDs, timestamps, or sequential numbers.
What Problem Does It Solve?
Organizing photos, scripts, or downloads often means renaming dozens of files at once. Most solutions:
- Require installation (pip, npm, etc.)
- Have complex configuration
- Overcomplicate simple renaming tasks
batch-namer is a single Python script you can copy anywhere and run immediately. It works in shared containers, offline environments, or systems with restricted package managers.
Installation and Usage
Quick Start
# Clone the repo
git clone https://github.com/Poolion/batch-namer.git
cd batch-namer
# Make executable (Linux/Mac)
chmod +x file_renamer.py
# Run with help
python3 file_renamer.py --help
Basic Command Structure
python3 file_renamer.py \
--source <directory> \
--dest <output-or-same> \
--pattern "*.jpg" \
[rename-options]
Rename with Custom Prefix
Add a date-based prefix to all images:
python3 file_renamer.py \
--source photos/ \
--dest photos/ \
--prefix "IMG_2024-10-30_120000" \
--pattern "*.jpg"
This renames vacation.jpg to IMG_2024-10-30_120000vacation.jpg.
Rename with UUIDs
Generate unique identifiers for files:
python3 file_renamer.py \
--source temp/ \
--dest backup/ \
--uuid \
--pattern "*.py"
Each .py file gets a 16-character hex UUID.
Sequential Numbering
Sort documents with numbers:
python3 file_renamer.py \
--source projects/docs/ \
--dest docs_sorted/ \
--number \
--pattern "*.md"
Files become 1.md, 2.md, 3.md, etc.
Preview with Dry Run
See what would happen before making changes:
python3 file_renamer.py \
--source photos/ \
--dest photos/ \
--pattern "*.jpg" \
--prefix "BACKUP_" \
--dry-run
Available Renaming Modes
| Option | Flag | Example Output |
|---|---|---|
| Prefix |
--prefix, -px
|
IMG_2024-10-30_vacation.jpg |
| UUID | --uuid |
a1b2c3d4e5f67890.jpg |
| Date |
--date, -dt
|
20241030_120500vacation.jpg |
| Sequence | --number |
1.jpg, 2.jpg
|
Date Format
The default date format is %Y%m%d_%H%M%S (e.g., 20241030_120000). Customize with:
python3 file_renamer.py --date "IMG-%Y-%m-%d" --prefix "".jpg"
Safety Features
Dry Run Mode
Before renaming anything, preview changes with --dry-run:
python3 file_renamer.py --source photos/ \
--dest photos/ \
--pattern "*.jpg" \
--prefix "BACKUP_" \
--dry-run
The tool displays all target renames without touching files.
Conflict Resolution
If a destination filename already exists, batch-namer increments a counter rather than overwriting:
Target: IMG_2024-10-30_vacation.jpg (exists)
→ IMG_2024-10-30_vacation_1.jpg (new)
This protects against accidental data loss when batch-rename operations encounter duplicates.
Pattern Matching
By default, patterns like "*.jpg" exclude:
- Hidden files (
.bash_profile) - System files (
.DS_Store,__MACOSX/) - Git directories (
.git/*)
Use !pattern to explicitly exclude (e.g., "*.jpg" "!*backup*")
Code Implementation Details
The tool's core logic centers on three functions:
Pattern to Regex Conversion
Glob patterns need conversion to regex for filesystem matching:
def pattern_to_regex(pattern: str) -> str:
# Escape special characters except glob wildcards
regex = ""
for char in pattern:
if char == "*":
regex += "[^/]*" # Any characters except /, including none
elif char == "?":
regex += "." # Any single character
elif char in r"\.^$+{}[]|()<>\\":
regex += "\\" + char
else:
regex += char
return f"^{regex}$"
This preserves the user's intent (e.g., "*.jpg" matches photo.jpg, but not photos/photo.jpg).
File Discovery
The tool uses Python's built-in glob to expand patterns:
def get_files(patterns, directory):
path = Path(directory)
files = []
for pattern in patterns:
expanded = path.glob(pattern)
files.extend(expanded)
return sorted(files, key=lambda f: f.name.lower())
Files are deduplicated and sorted if a single pattern matches multiple directories with overlapping names.
Safe Renaming Function
Each rename mode uses the same conflict-resolution pattern:
def rename_with_prefix(files, prefix):
for file_path in files:
new_name = f"{prefix}{file_path.stem}{file_path.suffix}"
new_path = file_path.parent / new_name
# Handle filename conflicts
counter = 1
while new_path.exists():
base, ext = os.path.splitext(file_path.name)
new_name = f"{prefix}{base}_{counter}.{ext}" if ext else f"{prefix}file_{counter}"
new_path = file_path.parent / new_name
counter += 1
shutil.move(str(file_path), str(new_path))
The while loop keeps incrementing until finding an unused filename, protecting against data loss.
When to Use batch-namer
This tool excels in:
- Photo organization: Batch-naming downloaded images from cameras
- Script management: Versioning temporary Python scripts with UUIDs
- Cleanup operations: Moving "temp/*" files with sequential numbers
- Portability: Copy the script to any environment without dependencies
- Offline work: No network or package manager needed
Limitations
- Python 3.x only (standard library, no external dependencies)
- Single-threaded (rename one file at a time for safety)
- Pattern matching via glob, not complex regex customization
- No GUI interface (it's meant to be headless/terminal-based)
Conclusion
batch-namer demonstrates the power of minimalism: a single script using only Python standard library modules to solve a common problem. If you work with files in bulk and need consistent renaming without dependency hell, give it a try.
Source Code: https://github.com/Poolion/batch-namer
Support Development: https://www.buymeacoffee.com/poolion
Top comments (0)