Shore Up
A stack of paper folders on a desk, each with a handwritten label; a hand crossing out one word on every label and writing a new word above it, with one folder set aside untouched.
Windows

Batch-Rename Files by Pattern with a Windows Batch Script

Ketan Aagja7 min read
No ratings yet

Before you run this

This is a cmd.exe batch file that renames files in one folder by find-and-replace: it takes every file matching a mask (say *.txt), swaps a substring in the filename (say draftfinal), and renames it. Its purpose is bulk cleanup of filenames — removing a stray word, fixing a prefix, changing a spelling — without you clicking through Explorer.

  • Privileges: No administrator rights are needed as long as you own the files and the folder isn't a protected system location. Renaming files under C:\Windows, C:\Program Files, or another user's profile would need an elevated prompt, and you should not be doing that anyway. Run it as your normal user.
  • Test first: Read the script before you run it. Point it at a copy of a folder — not the real one — the first time. The version below ships with a dry-run switch turned on by default, so it prints what it would rename and touches nothing until you deliberately flip it off. Use that.
  • This changes data, and there is no automatic undo. A batch ren does not go into the Explorer "Undo" history — pressing Ctrl+Z in a window later will not reverse it. The rename is done when it's done. It is reversible by hand only if the change is clean (see the Undo section), so treat the first real run with the same care you'd give any bulk operation.
  • Collisions: ren will not silently overwrite an existing file — if the target name already exists it errors out. The script checks for that and skips, rather than clobbering. Substring matching in cmd is case-insensitive, so Draft, draft, and DRAFT all match; know that going in.

Assumed environment: Windows 10 or Windows 11, run from cmd.exe (the classic Command Prompt), operating on a single folder, non-recursively. If you'd rather do this in PowerShell, the standard path there is Get-ChildItem piped to Rename-Item -WhatIf — that's a different guide; I'm doing the batch-file way here.

The simplest case: change an extension

If all you want is to change the extension on a batch of files, you don't need a script at all. The built-in ren command takes wildcards:

ren *.jpeg *.jpg

That renames every .jpeg in the current directory to .jpg. It's the right tool when the pattern is a clean extension swap. It gets unreliable the moment you try to do partial-name surgery with wildcards, though — cmd's wildcard rename has genuinely surprising edge behaviour. For anything more than an extension change, use the loop below.

The script: find-and-replace in filenames

Save this as rename-pattern.bat. Edit the five variables at the top, then run it.

@echo off
setlocal enabledelayedexpansion

rem === Settings you edit ===
set "TARGETDIR=C:\path\to\files"
set "FILEMASK=*.txt"
set "FIND=draft"
set "REPLACE=final"
set "DRYRUN=1"          rem 1 = preview only, 0 = actually rename

rem === Do not edit below this line ===
pushd "%TARGETDIR%" || (echo Cannot open "%TARGETDIR%" & exit /b 1)

for %%F in (%FILEMASK%) do (
    set "OLD=%%~nxF"
    rem substring replace: !OLD! with FIND swapped for REPLACE
    set "NEW=!OLD:%FIND%=%REPLACE%!"
    if not "!OLD!"=="!NEW!" (
        if "%DRYRUN%"=="1" (
            echo [DRY RUN] "!OLD!"  --^>  "!NEW!"
        ) else if exist "!NEW!" (
            echo SKIP    : "!NEW!" already exists, leaving "!OLD!" alone
        ) else (
            ren "!OLD!" "!NEW!"
            echo RENAMED : "!OLD!"  --^>  "!NEW!"
        )
    )
)

popd
endlocal

What the non-obvious lines do:

  • setlocal enabledelayedexpansion lets me read a variable I just set inside the same loop iteration, using !OLD! instead of %OLD%. Without it, the loop would use the value from before the loop started — a classic cmd trap.
  • %%~nxF is the file's name plus extension with any path stripped. In a batch file the loop variable takes %%F; on the command line directly it would be %F.
  • set "NEW=!OLD:%FIND%=%REPLACE%!" is the substitution. cmd's %var:old=new% replaces every occurrence of old in the string, not just the first. So FIND=a, REPLACE=b on banana gives bbnbnb. Keep your FIND string specific.
  • The if not "!OLD!"=="!NEW!" guard means files that don't contain the pattern are ignored, so the output only lists what actually changed.
  • pushd/popd move into and back out of the target folder so ren operates there. ren cannot move a file between folders — it renames in place only.

Run it

Open Command Prompt, cd to wherever you saved it, and:

rename-pattern.bat

With DRYRUN=1 it prints lines like:

[DRY RUN] "q1-draft-report.txt"  --> "q1-final-report.txt"
[DRY RUN] "draft-notes.txt"  --> "final-notes.txt"

Read that list. If — and only if — every proposed rename is correct, edit the file, set DRYRUN=0, and run it again to commit.

Common variations

  • Add or remove a prefix: to strip a leading IMG_, set FIND=IMG_ and REPLACE= (empty). To add a prefix you can't do it with this replace logic; you'd rename to PREFIX_!OLD! instead — that's a one-line change to the ren line, ren "!OLD!" "PREFIX_!OLD!", but test it on a copy because it has its own collision cases.
  • Recurse into subfolders: swap the loop for for /r "%TARGETDIR%" %%F in (%FILEMASK%) do (...) and use ren "%%F" "!NEW!" with the full path. Recursion multiplies the blast radius, so dry-run it hard.
  • Case sensitivity: cmd's substring replace is case-insensitive and there's no switch to change that. If you need case-sensitive matching, this isn't the right tool — do it in PowerShell.

Verify it worked

List the folder and confirm the names are what you expect:

dir /b "C:\path\to\files"

/b gives bare filenames, one per line, which is easy to eyeball or pipe into a file for a before/after comparison:

dir /b "C:\path\to\files" > after.txt

If you captured a dir /b > before.txt beforehand, fc before.txt after.txt shows exactly what changed.

Undo

There is no automatic rollback. If the change was clean — a simple, unique substring swap with no skipped collisions — you can reverse it by running the same script with FIND and REPLACE swapped. For the example above, set FIND=final and REPLACE=draft, dry-run it, and confirm before committing.

That reversal is only safe when nothing was skipped and the replacement string didn't already occur in the original names. If the output during the real run showed any SKIP lines, or the pattern was ambiguous, do not trust a blind reverse — restore those files from your copy or backup instead. This is exactly why the first run should be against a copy of the folder.

For the official reference on ren, for, and variable substring substitution, see the Windows Commands documentation on Microsoft Learn (search "ren command" and "for command").

Written by
Ketan Aagja

Runs enterprise networks and security for a living, and writes Shore Up to turn two decades of hands-on Linux, Windows and mail-server work into guides you can actually use.

More about the author →

Was this article helpful?

Tap a star — no sign-in needed.

Be the first to rate this article.