How to Create a Bash File Renamer

How to Create a Bash File Renamer

How to Create a Bash File Renamer

I can’t count how many times I’ve downloaded a folder full of photos named IMG_0001.jpg, IMG_0002.jpg, and so on, and wished they had meaningful names with dates or sequential numbering that actually made sense. Doing that by hand, one file at a time, is soul-crushing. That’s exactly why I built my own Bash file renamer, and over time I’ve refined it into a tool I now reach for constantly. Here’s everything I’ve learned about building one properly.

Why Use Bash for Batch Renaming

Renaming a handful of files manually is fine. Renaming hundreds or thousands is a different story. Bash gives you:

The Core Command: mv

At its most basic, renaming a file in Bash is just:

mv old_name.txt new_name.txt

A file renamer script is essentially a loop that calls mv intelligently across many files based on some pattern or rule.

A Basic Batch Renamer

Let’s say I want to rename all .jpeg files to .jpg:

#!/bin/bash

for file in *.jpeg; do
    [ -e "$file" ] || continue
    mv -- "$file" "${file%.jpeg}.jpg"
done

echo "Renaming complete."

How This Works Internally

Adding Sequential Numbering

Here’s a script I use constantly for renaming a batch of photos into a clean sequential format:

#!/bin/bash

set -euo pipefail

prefix="vacation_photo"
counter=1

for file in *.jpg; do
    [ -e "$file" ] || continue
    new_name=$(printf "%s_%03d.jpg" "$prefix" "$counter")
    mv -- "$file" "$new_name"
    echo "Renamed $file -> $new_name"
    ((counter++))
done

Breaking It Down

Renaming Based on File Modification Date

Sometimes you want filenames to reflect metadata like creation or modification time, which is great for organizing camera exports or log files.

#!/bin/bash

set -euo pipefail

for file in *.jpg; do
    [ -e "$file" ] || continue
    timestamp=$(date -r "$file" +"%Y%m%d_%H%M%S")
    mv -- "$file" "${timestamp}.jpg"
done

Here, date -r "$file" reads the file’s last modification time and formats it into a sortable timestamp string, which then becomes the new filename.

Using sed for Pattern-Based Renaming

If you need more advanced text substitution — like replacing spaces with underscores or stripping unwanted characters — sed combined with a loop works well:

#!/bin/bash

set -euo pipefail

for file in *; do
    [ -f "$file" ] || continue
    new_name=$(echo "$file" | sed -e 's/ /_/g' -e 's/[^A-Za-z0-9._-]//g')
    if [ "$file" != "$new_name" ]; then
        mv -- "$file" "$new_name"
        echo "Renamed: $file -> $new_name"
    fi
done

This replaces spaces with underscores and strips out any character that isn’t alphanumeric, a period, underscore, or hyphen — handy for cleaning up messy filenames before uploading them somewhere strict about naming conventions.

A Dry-Run Mode for Safety

I never trust a renaming script fully until I’ve tested it. Adding a dry-run flag has saved me from disaster more than once:

#!/bin/bash

set -euo pipefail

dry_run=false
if [[ "${1:-}" == "--dry-run" ]]; then
    dry_run=true
fi

for file in *.txt; do
    [ -e "$file" ] || continue
    new_name="renamed_${file}"
    if [ "$dry_run" = true ]; then
        echo "[DRY RUN] Would rename: $file -> $new_name"
    else
        mv -- "$file" "$new_name"
        echo "Renamed: $file -> $new_name"
    fi
done

Running ./renamer.sh --dry-run first lets you preview every change before committing to it.

Real-World Use Cases

Automation Example

Here’s a renamer I run via cron to standardize files dropped into a shared folder every night:

#!/bin/bash

set -euo pipefail

target_dir="/data/incoming"
log_file="/var/log/renamer.log"

cd "$target_dir"

for file in *; do
    [ -f "$file" ] || continue
    clean_name=$(echo "$file" | tr ' ' '_' | tr -cd 'A-Za-z0-9._-')
    if [ "$file" != "$clean_name" ] && [ -n "$clean_name" ]; then
        mv -- "$file" "$clean_name"
        echo "$(date '+%F %T') Renamed '$file' -> '$clean_name'" >> "$log_file"
    fi
done

This keeps a shared directory tidy automatically, logging every rename for auditing purposes.

Best Practices

Security Considerations

Optimization Tips

Troubleshooting Common Issues

Problem: “No such file or directory” errors during renaming. This usually means the glob pattern didn’t match any files, and the literal pattern string was passed to mv. Always guard loops with [ -e "$file" ] || continue.

Problem: Filenames with spaces break the script. Ensure every variable reference is quoted ("$file", not $file).

Problem: Some files get skipped. Check whether your glob pattern is case-sensitive — *.JPG and *.jpg won’t match the same files unless you enable shopt -s nocaseglob.

Problem: Script renames files in the wrong order. Bash’s default glob expansion is alphabetical. If you need chronological order, sort explicitly using find ... -printf '%T@ %p\n' | sort -n.

Common Mistakes to Avoid

Frequently Asked Questions

Can I undo a batch rename if something goes wrong? Only if you logged the original-to-new filename mapping beforehand. I recommend always writing a log file of renames so you can reverse them if needed.

Does Bash have a built-in rename command? Not natively — rename is a separate utility (often Perl-based) available on many distributions, but writing your own loop with mv gives you full control without depending on it.

How do I rename files recursively through subdirectories? Use find /path -type f -name "*.jpg" -exec bash -c 'mv "$1" "${1%.jpg}.jpeg"' _ {} \; or loop over find ... -print0 output.

Will this work on macOS as well as Linux? Mostly yes, though some commands like date and sed have different flag behavior on BSD-based macOS versus GNU/Linux — test carefully or install GNU coreutils via Homebrew for consistency.

How do I handle filenames with Unicode characters? Make sure your terminal and script locale are set to UTF-8 (export LC_ALL=en_US.UTF-8) to avoid corrupting non-ASCII filenames during renaming.

Summary

A Bash file renamer starts as a simple loop around mv, but it becomes genuinely powerful once you add sequential numbering, timestamp-based naming, pattern cleanup with sed, and a dry-run safety mode. The biggest lessons I’ve learned: always quote your variables, always test with a dry run first, and always log what you renamed so you can undo it if something goes wrong.

References

Exit mobile version