【bat】ファイル名の文字列を一括置換するバッチファイル完全ガイド|!VAR:old=new!・ドライラン・再帰・特殊文字対策・実践パターンまで

Windowsでファイル名の特定の文字列を置換するバッチファイルの作り方 bat

report_2023」を全部「report_2024」に変えたい、スペースをアンダースコアに統一したい──こうした文字列置換を手動でやると100ファイルで数十分かかります。バッチファイルなら 30秒以内 で完了します。

この記事では、バッチの !VAR:old=new! 構文 を使ったファイル名一括置換を、ドライラン・再帰処理・特殊文字対策・実践パターンまで段階的に解説します。

スポンサーリンク

目次

  1. 仕組み:!VAR:old=new! とは
  2. 基本形:単一フォルダの文字列置換
  3. ドライランで安全に確認する
  4. サブフォルダを再帰処理する
  5. 特定拡張子のみ対象にする
  6. 複数の置換を連鎖させる
  7. 大文字・小文字の扱い
  8. 落とし穴5選
  9. 実践例3本
  10. よくある質問(FAQ)
  11. まとめ

仕組み:!VAR:old=new! とは

バッチファイルでファイル名の文字列を置換するコアは、遅延展開(Delayed Expansion)の文字列置換演算子 です。

構文:!変数名:置換前=置換後!

変数の値の中で「置換前」に一致するすべての箇所を「置換後」に置き換えた文字列を返します。

setlocal enabledelayedexpansion

set "FILE=report_2023_final.txt"

:: "2023" を "2024" に置換
set "RESULT=!FILE:2023=2024!"
echo !RESULT!   :: → report_2024_final.txt

:: スペースをアンダースコアに置換
set "FILE2=my report file.txt"
set "RESULT2=!FILE2: =_!"
echo !RESULT2!  :: → my_report_file.txt

ポイントは3つです。

  • 大文字・小文字を区別しない(後述)
  • 一致するすべての箇所 を置換する(最初の1つだけでなく全部)
  • setlocal enabledelayedexpansion! 記法が必須

基本形:単一フォルダの文字列置換

@echo off
setlocal enabledelayedexpansion

set "TARGET_DIR=C:\work\files"  :: 対象フォルダのパス
set "SEARCH=old_string"           :: 置換前の文字列
set "REPLACE=new_string"          :: 置換後の文字列

pushd "%TARGET_DIR%"
for %%F in (*) do (
    set "FNAME=%%~nxF"
    set "NEWNAME=!FNAME:%SEARCH%=%REPLACE%!"
    :: 変化がない場合は ren をスキップ(同名ファイルへの ren はエラー)
    if "!NEWNAME!" NEQ "!FNAME!" (
        ren "%%F" "!NEWNAME!"
        echo [DONE] "%%F"  -->  "!NEWNAME!"
    )
)
popd
endlocal
echo 完了

ポイント!FNAME:%SEARCH%=%REPLACE%! の中で %SEARCH%%REPLACE% は通常展開(% 記法)で埋め込みます。こうすることで「変数の値を使った置換パターン」を実現できます。

また、if "!NEWNAME!" NEQ "!FNAME!" のチェックが重要です。置換前後で名前が変わらないファイルに ren を実行するとエラー(「The filename, directory name, or volume label syntax is incorrect.」)になるため、必ずガードを入れてください。

ドライランで安全に確認する

大量のファイルを一括変更する前に 変更後のファイル名を確認 するドライランは必須ステップです。

@echo off
setlocal enabledelayedexpansion

set "TARGET_DIR=C:\work\files"
set "SEARCH=old"
set "REPLACE=new"
set "DRYRUN=1"   :: 0 にすると実際に変更する

pushd "%TARGET_DIR%"
set /a MATCH_COUNT=0
for %%F in (*) do (
    set "FNAME=%%~nxF"
    set "NEWNAME=!FNAME:%SEARCH%=%REPLACE%!"
    if "!NEWNAME!" NEQ "!FNAME!" (
        set /a MATCH_COUNT+=1
        if "!DRYRUN!"=="1" (
            echo [DRY] "%%F"  -->  "!NEWNAME!"
        ) else (
            ren "%%F" "!NEWNAME!"
            echo [DONE] "%%F"  -->  "!NEWNAME!"
        )
    )
)
popd
endlocal
echo 対象: !MATCH_COUNT! 件
if "!DRYRUN!"=="1" echo ※ DRYRUN=0 にすると実際に変更されます

サブフォルダを再帰処理する

フォルダ配下のすべてのサブフォルダも含めて処理する場合は for /r を使います。

@echo off
setlocal enabledelayedexpansion

set "TARGET_DIR=C:\work\files"
set "SEARCH=draft"
set "REPLACE=final"

:: /r でサブフォルダも再帰的に処理
for /r "%TARGET_DIR%" %%F in (*) do (
    set "FNAME=%%~nxF"
    set "NEWNAME=!FNAME:%SEARCH%=%REPLACE%!"
    if "!NEWNAME!" NEQ "!FNAME!" (
        ren "%%F" "!NEWNAME!"
        echo [DONE] %%F
    )
)
endlocal
echo 完了

注意for /r はフォルダ名自体はリネームしません(ファイルのみ)。フォルダ名も置換したい場合は別途 for /d /r を使うか、【bat】ファイル名の先頭にフォルダ名を一括で付ける方法 を参考に組み合わせてください。

特定拡張子のみ対象にする

フォルダに複数種類のファイルが混在している場合は、for %%F in (*.txt) で絞り込めます。

@echo off
setlocal enabledelayedexpansion

set "TARGET_DIR=C:\work\files"
set "SEARCH=2023"
set "REPLACE=2024"

pushd "%TARGET_DIR%"
:: .txt と .csv のみ対象
for %%F in (*.txt *.csv) do (
    set "FNAME=%%~nxF"
    set "NEWNAME=!FNAME:%SEARCH%=%REPLACE%!"
    if "!NEWNAME!" NEQ "!FNAME!" (
        ren "%%F" "!NEWNAME!"
        echo [DONE] "%%F"  -->  "!NEWNAME!"
    )
)
popd
endlocal

複数の置換を連鎖させる

複数の置換を連鎖させる場合は、ステップごとに変数を更新 して次の置換に渡します。

@echo off
setlocal enabledelayedexpansion

set "TARGET_DIR=C:\work\files"

pushd "%TARGET_DIR%"
for %%F in (*) do (
    set "FNAME=%%~nxF"
    :: ステップ1: スペースをアンダースコアに
    set "TMP=!FNAME: =_!"
    :: ステップ2: ハイフンをアンダースコアに
    set "TMP=!TMP:-=_!"
    :: ステップ3: 2023 を 2024 に
    set "NEWNAME=!TMP:2023=2024!"
    if "!NEWNAME!" NEQ "!FNAME!" (
        ren "%%F" "!NEWNAME!"
        echo [DONE] "%%F"  -->  "!NEWNAME!"
    )
)
popd
endlocal

中間変数 TMP を使って段階的に変換することで、複雑な置換も整理して書けます。バッチでの文字列操作の詳細は「【bat】バッチファイルでの文字列置換方法」も参照してください。

大文字・小文字の扱い

!VAR:old=new! はデフォルトで 大文字・小文字を区別しません(case-insensitive)。

setlocal enabledelayedexpansion

:: "Report" も "REPORT" も "report" も置換される
set "FILE=REPORT_2023.txt"
set "RESULT=!FILE:report=summary!"
echo !RESULT!   :: → summary_2023.txt

set "FILE2=Report_2023.txt"
set "RESULT2=!FILE2:report=summary!"
echo !RESULT2!  :: → summary_2023.txt

これはほとんどのケースで便利ですが、大文字・小文字を区別した置換が必要な場合はバッチでは直接サポートされていません。その場合は PowerShell の -creplace 演算子(case-sensitive replace)との組み合わせが必要になります。

落とし穴5選

落とし穴1:置換前後が同じ場合に ren がエラーになる

ファイル名に検索文字列が含まれない場合、NEWNAMEFNAME と同じ値になります。同名ファイルへの ren はエラー を出すため、必ず if "!NEWNAME!" NEQ "!FNAME!" でガードします(基本形で対処済み)。

落とし穴2:変数に ! が含まれる場合の問題

setlocal enabledelayedexpansion が有効なとき、ファイル名や変数値に ! が含まれると ! が遅延展開の区切りとして解釈 されて値が壊れます。

:: NG:ファイル名に ! が含まれる場合
:: ファイル名 "report!2023.txt" を処理すると !2023. が展開記号として解釈される
for %%F in (*) do (
    set "FNAME=%%~nxF"          :: ← ここで ! 以降が消える
    set "NEWNAME=!FNAME:old=new!"
    ren "%%F" "!NEWNAME!"
)

:: 対策:setlocal enabledelayedexpansion を使わず、for の %%F から直接置換
:: (この場合は PowerShell を使う方が確実)
powershell -Command "Get-ChildItem | Where-Object { $_.Name -like '*old*' } | Rename-Item -NewName { $_.Name -replace 'old','new' }"

落とし穴3:% 記号がファイル名に含まれる場合

バッチファイル内で % は変数参照の記号として解釈されます。ファイル名に % が含まれる場合は %% でエスケープが必要ですが、!VAR:...! での置換時には %% を使えないケースがあります。% を含むファイル名の操作は PowerShell を使うことを推奨します。

落とし穴4:置換後のファイル名が空・拡張子のみになる危険性

ファイル名全体が検索文字列と一致する場合(例:ファイル名が draft、検索文字列が draft)、置換後の名前部分が空文字になります。拡張子のみのファイル名(.txt)はWindowsでは作成できない ためエラーになります。

:: NG:ファイル名が "draft.txt"、SEARCH="draft"、REPLACE="" の場合
:: → NEWNAME が ".txt" になり ren がエラーになる

:: 対策:置換後の名前部分が空でないかチェック
for %%F in (*) do (
    set "FNAME=%%~nxF"
    set "NEWNAME=!FNAME:%SEARCH%=%REPLACE%!"
    :: 拡張子なしの名前部分が空でないことを確認
    set "NEWBASE=!NEWNAME:%%~xF=!"
    if "!NEWBASE!" NEQ "" (
        if "!NEWNAME!" NEQ "!FNAME!" (
            ren "%%F" "!NEWNAME!"
        )
    ) else (
        echo [SKIP] 置換後のファイル名が空になります: %%F
    )
)

落とし穴5:日本語ファイル名の文字化け

バッチファイルのデフォルト文字コードはShift-JIS(CP932)です。UTF-8のファイル名を扱う場合や他のシステムで作成されたファイルは文字化けすることがあります。詳細は「【bat】日本語ファイル名を扱うときの注意点と解決策」を参照してください。

実践例3本

実践例1:バージョン番号を一括更新(2023→2024)

年度末・年度初めに多いバージョン更新作業。確認ダイアログ付きで安全に実行します。

@echo off
setlocal enabledelayedexpansion

set "TARGET_DIR=C:\work\reports"
set "SEARCH=2023"
set "REPLACE=2024"

echo === 置換プレビュー ===
pushd "%TARGET_DIR%"
set /a COUNT=0
for %%F in (*) do (
    set "FNAME=%%~nxF"
    set "NEWNAME=!FNAME:%SEARCH%=%REPLACE%!"
    if "!NEWNAME!" NEQ "!FNAME!" (
        set /a COUNT+=1
        echo   "%%F"  -->  "!NEWNAME!"
    )
)
echo.
echo 対象ファイル数: !COUNT!
echo.
set /p CONFIRM=実行しますか?(y/n): 
if /i "!CONFIRM!" NEQ "y" (
    echo キャンセルしました
    popd & endlocal & exit /b
)

for %%F in (*) do (
    set "FNAME=%%~nxF"
    set "NEWNAME=!FNAME:%SEARCH%=%REPLACE%!"
    if "!NEWNAME!" NEQ "!FNAME!" ren "%%F" "!NEWNAME!"
)
popd
endlocal
echo 完了

実践例2:スペースをアンダースコアに置換(Webアップロード前の正規化)

Webサーバーやクラウドストレージにアップロードする前にスペースを _ に変換します。スペース以外の問題文字(ハイフン・括弧)もまとめて処理します。

@echo off
setlocal enabledelayedexpansion

set "TARGET_DIR=C:\work\upload"

pushd "%TARGET_DIR%"
for %%F in (*) do (
    set "FNAME=%%~nxF"
    :: スペースをアンダースコアに
    set "TMP=!FNAME: =_!"
    :: ( ) を削除(空文字へ置換 ※環境依存あり)
    set "TMP=!TMP:(=!"
    set "NEWNAME=!TMP:)=!"
    if "!NEWNAME!" NEQ "!FNAME!" (
        ren "%%F" "!NEWNAME!"
        echo [DONE] "%%F"  -->  "!NEWNAME!"
    )
)
popd
endlocal
echo 完了

実践例3:バックアップファイル名の日付形式を変換(YYYY-MM-DD → YYYYMMDD)

システムが出力した backup-2024-01-15.sql 形式のファイルを backup_20240115.sql に変換して管理しやすくします。

@echo off
setlocal enabledelayedexpansion

set "TARGET_DIR=C:\work\backup"

pushd "%TARGET_DIR%"
for %%F in (*.sql *.bak *.zip) do (
    set "FNAME=%%~nxF"
    :: ハイフンをアンダースコアに(日付セパレータ変換)
    set "TMP=!FNAME:-=!"
    :: ファイル名先頭の backup- の処理(ここでは変換済みで backup に)
    if "!TMP!" NEQ "!FNAME!" (
        :: "backup" を "backup_" に(先頭の区切りだけアンダースコアを戻す)
        set "NEWNAME=!TMP:backup=backup_!"
    ) else (
        set "NEWNAME=!FNAME!"
    )
    if "!NEWNAME!" NEQ "!FNAME!" (
        ren "%%F" "!NEWNAME!"
        echo [DONE] "%%F"  -->  "!NEWNAME!"
    )
)
popd
endlocal
echo 完了

より複雑な日付形式の変換については、「【bat】日付と時間をファイル名に挿入する方法完全ガイド」も参照してください。

よくある質問(FAQ)

Q. 置換する文字列が見つからない場合でもエラーにしたい
A. 基本形のコードでは置換なしのファイルを自動的にスキップしますが、「1件も置換されなかった場合に警告を出す」にはカウンター変数を使います。set /a MATCH_COUNT=0 で初期化し、置換が発生したときに set /a MATCH_COUNT+=1 でインクリメント、ループ後に if !MATCH_COUNT! EQU 0 echo 対象ファイルが見つかりませんでした とチェックします。
Q. ファイル名だけでなく拡張子内の文字列も置換したい
A. %%~nxF(名前+拡張子)を使っていれば拡張子内も置換されます。拡張子を変換したい場合は「【bat】ファイルの拡張子を一括変換する方法完全ガイド」を参照してください。
Q. 大文字・小文字を区別して置換したい
A. バッチの !VAR:old=new! は常にcase-insensitive(大文字小文字無視)です。区別が必要な場合はPowerShellを利用してください。
Get-ChildItem | Where-Object { $_.Name -clike "*old*" } | Rename-Item -NewName { $_.Name -creplace "old","new" }
Q. ファイル名の先頭や末尾に文字を追加するには?
A. 先頭・末尾への追加は 【bat】ファイル名の先頭・末尾に文字を一括追加するバッチファイル完全ガイド を参照してください。%%~nF(名前のみ)+ %%~xF(拡張子)を組み合わせる方法を解説しています。
Q. 変数に余分な空白が入ってうまく置換されない
A. set "VAR=value" のようにダブルクォートで囲んで設定していれば末尾の空白は入りません。クォートなしで set VAR=value と書くと末尾スペースが変数に含まれます。詳細は「【bat】変数の末尾に余計な空白が入ってしまうときの原因と対策」を参照してください。
Q. 置換後のファイル名が既に存在する場合はどうなる?
A. ren は既存ファイルを上書きしません。同名ファイルが存在する場合はエラーになります。事前にドライランで確認し、重複が予想される場合は番号サフィックスを追加するなど工夫が必要です。

まとめ

バッチファイルでファイル名の文字列を一括置換するポイントをまとめます。

  • コア構文!FNAME:置換前=置換後!(遅延展開必須)
  • 同名チェックif "!NEWNAME!" NEQ "!FNAME!"ren エラーを防ぐ
  • 大文字小文字!VAR:old=new! はcase-insensitive(区別しない)
  • 連鎖置換:中間変数 TMP を使って段階的に適用
  • ドライランDRYRUN=1 フラグで変更前に必ず確認
  • 特殊文字! % を含むファイル名はPowerShellで対処

関連記事もあわせてご覧ください。