1 unstable release
| 0.1.0 | Sep 6, 2025 |
|---|
#685 in Command line utilities
26KB
466 lines
fnr - File Name Rename π
Because life's too short to rename files one by one like a caveman
What is this sorcery?
fnr is a blazingly fastβ’ file and directory renaming tool that will make you question why you ever used mv in a for loop like some kind of shell script peasant. It's like sed for filenames, but with more colors, gitignore support, and less existential dread.
Why does this exist?
Have you ever had 47 files named component_something.rs and needed to rename them all to ui_something.rs? Have you ever stared at a directory full of test_*.py files and wished they were spec_*.py instead? Have you ever wanted to batch rename files without writing a bash script that looks like it was written by a caffeinated squirrel?
This is your salvation.
Installation
cargo install fnr-tool
# Or clone this repo and `cargo build --release` like the cool kids do
Usage
Basic Renaming (The Bread and Butter)
# Rename all files containing "old" to "new"
fnr "old" "new"
# Preview what would happen (because trust issues)
fnr "old" "new" --dry-run
# Use specific glob patterns
fnr "component" "ui" "**/*.rs"
# Multiple glob patterns (the power move)
fnr "test" "spec" "**/*.py" "**/*.js" "!node_modules/**"
# With base directory
fnr "old" "new" "**/*.rs" --base-dir /path/to/project
Advanced Wizardry (Multiple Patterns & Exclusions)
# Match multiple file types at once
fnr "component" "ui" "*.{rs,ts,js}" "*.toml"
# Exclude specific directories
fnr "old" "new" "**/*" "!target/**" "!node_modules/**"
# Search from a different base directory
fnr "config" "settings" "**/*.ini" --base-dir /path/to/configs
# Control search depth
fnr "test" "spec" "**/*.py" --max-depth 3 --min-depth 1
Regex Mode (For the Regex Wizards)
# Use regex because you're fancy
fnr --regex "test_(.+)" "spec_$1" "**/*.py"
# Rename all your poorly named components
fnr --regex "component_(.+)" "ui_$1" "src/**/*.rs"
Interactive Mode (For the Cautious)
By default, fnr will ask you about each rename because it respects your trust issues:
./src/old_component.rs -> ./src/new_component.rs
Replace filename/dirname? [Y]es/[n]o/[a]ll/[q]uit:
Just press a single key (no Enter required, we're not animals):
y- Yes, rename this filen- No, skip this onea- Yes to ALL remaining files (YOLO mode)q- Quit and pretend this never happened
Flags for the Flag Enthusiasts
--dry-run # See what would happen without commitment
--no-interactive # YOLO mode (renames everything without asking)
--regex # Enable regex patterns for the power users
--type=file # Only rename files
--type=dir # Only rename directories
--type=both # Rename everything (default)
--no-recursive # Stay in current directory like a hermit
--case-sensitive # Because "Test" β "test" (obviously)
--hidden # Include hidden files (the secret ones)
--no-color # Remove all joy from your terminal
--no-symlink # Don't follow symbolic links
--no-skip-gitignore # Ignore .gitignore files (chaos mode)
--max-depth N # Maximum directory depth to search
--min-depth N # Minimum directory depth to search
--base-dir PATH # Base directory to search from
Examples That Will Change Your Life
The Classic "I Hate My Naming Convention" Scenario
# You have: component_button.rs, component_input.rs, component_modal.rs
# You want: ui_button.rs, ui_input.rs, ui_modal.rs
fnr "component" "ui" "**/*.rs"
The "My Tests Are Lying About Being Tests" Fix
# Convert test_*.py to spec_*.py because you're fancy now
fnr "test_" "spec_" "**/*.py"
The "Multiple File Types" Power Move
# Rename across multiple file types at once
fnr "old_api" "new_api" "**/*.{rs,ts,js,py}" "**/*.toml" "!target/**"
The "Regex Flex" Move
# Convert CamelCase to snake_case (sort of)
fnr --regex "([A-Z])" "_$1" --case-sensitive "**/*.rs"
The "I'm Feeling Dangerous" Approach
# Rename everything with "old" to "new" in the entire project
fnr "old" "new" "**/*" --no-interactive
# (Use with caution, we are not responsible for your life choices)
The "Gitignore Respecting Professional" Method
# Respects .gitignore by default (like a civilized human)
fnr "component" "ui" "**/*.rs"
# Chaos mode: ignore .gitignore
fnr "component" "ui" "**/*.rs" --no-skip-gitignore
Features That Will Make You Popular at Parties
- π Colorized output - Because monochrome is for printers
- β‘ Blazingly fast - Uses all your CPU cores (probably)
- π― Smart sorting - Renames files before directories to avoid chaos
- π Multiple glob patterns -
"**/*.rs" "**/*.toml" "!target/**"works like magic - π¨ Regex support - For when you want to feel superior
- π‘οΈ Safe by default - Interactive mode prevents disasters
- π« Gitignore aware - Respects your
.gitignoreautomatically - π Base directory support - Search from anywhere
- ποΈ Depth control -
--max-depthand--min-depthfor precision - β‘ Single-key interaction - No Enter key required in interactive mode
Syntax
fnr [OPTIONS] <PATTERN> [REPLACEMENT] [GLOB_PATTERNS...] [--base-dir BASE_DIR]
Examples:
# Single pattern
fnr "old" "new" "**/*.rs"
# Multiple patterns
fnr "component" "ui" "**/*.rs" "**/*.ts" "**/*.js"
# With exclusions
fnr "test" "spec" "**/*.py" "!venv/**" "!__pycache__/**"
# Different base directory
fnr "config" "settings" "**/*.ini" --base-dir /path/to/configs
# Brace expansion support
fnr "old" "new" "*.{rs,toml,lock}"
Color Scheme (Because Aesthetics Matter)
- White: File paths and unchanged parts
- Yellow: The parts being changed (both old and new)
- Cyan: Action prompts
- Green/Blue: File type indicators (f for files, d for directories)
Gitignore Support π
fnr respects .gitignore files by default! This means:
- No more accidentally renaming files in
target/,node_modules/, or.git/ - Follows the same ignore rules as your favorite tools
- Use
--no-skip-gitignoreif you want to live dangerously
Performance Notes
- Multiple patterns: Uses
globsetfor efficient simultaneous pattern matching - Directory traversal: Uses the
ignorecrate for fast, gitignore-aware walking - Smart ordering: Files are renamed before their parent directories
- Memory efficient: Streams results instead of loading everything into memory
Warning Signs You Need This Tool
- You've written
for file in *.txt; do mv "$file" "${file%.txt}.bak"; donemore than once - You have a folder called "New Folder (47)"
- You've ever used a GUI file manager to rename files one by one
- You think
renameis a Perl script (it is, and that's the problem) - You've considered learning sed just for filename manipulation
- You manually exclude
node_modulesandtargetdirectories every time
Contributing
Found a bug? Want a feature? Think the colors are wrong? Open an issue or PR!
Just remember: this tool was built by someone who got tired of renaming files manually, so the bar for "useful contribution" is pretty low.
License
MIT - Because sharing is caring, and lawyers are expensive.
Disclaimer
fnr is not responsible for:
- Accidentally renaming your entire home directory
- Making your coworkers jealous of your file organization skills
- Causing you to become overly obsessed with perfect naming conventions
- Any existential crises caused by realizing how much time you've wasted renaming files manually
- Addiction to using multiple glob patterns for everything
Made with β€οΈ and an unhealthy obsession with file organization and rational fear of the sed command
Remember: With great power comes great responsibility. Use --dry-run first, kids.
Dependencies
~8β21MB
~275K SLoC