#file-rename #batch #batch-rename #cli #glob

app fnr-tool

A blazingly fast file and directory renaming tool with gitignore support

1 unstable release

0.1.0 Sep 6, 2025

#685 in Command line utilities

MIT license

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 file
  • n - No, skip this one
  • a - 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
  • 🌈 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 .gitignore automatically
  • πŸ“ Base directory support - Search from anywhere
  • πŸŽ›οΈ Depth control - --max-depth and --min-depth for 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-gitignore if you want to live dangerously

Performance Notes

  • Multiple patterns: Uses globset for efficient simultaneous pattern matching
  • Directory traversal: Uses the ignore crate 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"; done more 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 rename is a Perl script (it is, and that's the problem)
  • You've considered learning sed just for filename manipulation
  • You manually exclude node_modules and target directories 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