遅延展開

にメンテナンス済み

バッチファイルで FOR ループや IF ブロックの中で変数の値を更新したのに、echo で表示すると古い値が表示される──。これはバッチファイルを書く上で非常によくある問題です。

この問題を解決するのが遅延環境変数展開(Delayed Expansion)です。

この記事では、なぜ通常の変数展開では問題が起きるのかを説明し、遅延展開の使い方を具体的なサンプルコードとともに解説します。

問題:ブロック内で変数が更新されない

まず、以下のバッチファイルを見てみましょう。

problem.cmd
@echo off
setlocal

set COUNT=0
for %%f in (*.txt) do (
    set /a COUNT+=1
    echo %COUNT% 個目: %%f
)
echo 合計: %COUNT% 個

endlocal

このバッチファイルでは、テキストファイルの数をカウントしようとしています。しかし、実行結果は期待通りになりません。

×
コマンド プロンプトのアイコン
コマンド プロンプト
Microsoft Windows [Version xx.x.xxxxx.xxx]
(c) 2026 Ribbit App Development All rights reserved.
 
C:\users\user>problem.cmd
0 個目: file1.txt
0 個目: file2.txt
0 個目: file3.txt
合計: 0 個
C:\users\user>

COUNT の値が常に 0 のまま表示されています。

原因:変数の展開タイミング

バッチファイルでは、括弧 () で囲まれたブロックは、実行前にまとめて解析されます。このとき %変数% はブロック全体が実行される前の値に展開されます。

つまり、上記の例では for ループに入る前の COUNT の値(0)が %COUNT% に展開され、ループ中に値を更新しても反映されないのです。

解析のイメージ
rem ブロック全体が解析される時点で %COUNT% は 0 に展開される
for %%f in (*.txt) do (
    set /a COUNT+=1
    echo 0 個目: %%f    ← %COUNT% が 0 に置き換わっている
)

解決方法:遅延展開を有効にする

遅延展開を有効にし、%変数% の代わりに !変数! を使用することで、変数が実行時に展開されるようになります。

手順

  1. setlocal enabledelayedexpansion を記述する
  2. ブロック内の変数参照を !変数名! に変更する
solution.cmd
@echo off
setlocal enabledelayedexpansion

set COUNT=0
for %%f in (*.txt) do (
    set /a COUNT+=1
    echo !COUNT! 個目: %%f
)
echo 合計: !COUNT! 個

endlocal
×
コマンド プロンプトのアイコン
コマンド プロンプト
Microsoft Windows [Version xx.x.xxxxx.xxx]
(c) 2026 Ribbit App Development All rights reserved.
 
C:\users\user>solution.cmd
1 個目: file1.txt
2 個目: file2.txt
3 個目: file3.txt
合計: 3 個
C:\users\user>

今度は COUNT の値が正しくカウントアップされ、期待通りの結果になりました。

%変数%!変数! の違い

記法展開タイミング使用場面
%変数%解析時(静的)ブロック外での通常の変数参照
!変数!実行時(動的)ブロック内で更新される変数の参照
遅延展開の有効範囲

setlocal enabledelayedexpansion の効果は、対応する endlocal までです。バッチファイル全体で有効にしたい場合は、ファイルの先頭で記述してください。

IF ブロックでも同様の問題が起きる

FOR ループだけでなく、IF ブロック内でも同じ問題が発生します。

問題のある例
@echo off
setlocal

set RESULT=NG

if exist "data.txt" (
    set RESULT=OK
    echo 結果: %RESULT%
)

上記では、data.txt が存在しても 結果: NG と表示されます。遅延展開を使って修正します。

修正後
@echo off
setlocal enabledelayedexpansion

set RESULT=NG

if exist "data.txt" (
    set RESULT=OK
    echo 結果: !RESULT!
)

遅延展開が必要な典型例

ファイル名に連番を付ける

numbering.cmd
@echo off
setlocal enabledelayedexpansion

set NUM=0
for %%f in (*.jpg) do (
    set /a NUM+=1
    set PADDED=00!NUM!
    set PADDED=!PADDED:~-3!
    ren "%%f" "photo_!PADDED!.jpg"
)

echo リネームが完了しました。
endlocal

ファイルの内容を変数に蓄積する

concat.cmd
@echo off
setlocal enabledelayedexpansion

set ALL_FILES=
for %%f in (*.txt) do (
    set ALL_FILES=!ALL_FILES! %%f
)

echo 見つかったファイル:!ALL_FILES!
endlocal

注意点

感嘆符 ! を含む文字列の扱い

遅延展開が有効な場合、文字列中の ! が変数展開の区切りとして解釈されます。ファイル名やパスに ! が含まれる場合は注意が必要です。

感嘆符を含むパスの問題
rem 遅延展開が有効な場合、以下のパスは正しく処理されない可能性がある
set PATH_WITH_BANG=C:\Important!Files
echo !PATH_WITH_BANG!
感嘆符を含む文字列

遅延展開が有効な場合、! が含まれる文字列は意図しない展開が行われる場合があります。感嘆符を含むパスを扱う必要がある場合は、その部分だけ遅延展開を無効にするか、^! でエスケープしてください。

必要な場面でのみ使用する

遅延展開は強力ですが、すべての場面で必要なわけではありません。ブロック外での単純な変数参照には通常の %変数% を使用してください。

まとめ

項目内容
有効化する方法setlocal enabledelayedexpansion
遅延展開の変数参照!変数名!
問題が起きる場面FOR ループや IF ブロックの中
注意点! を含む文字列が誤って解釈される

遅延展開は、バッチファイルでループやブロック内の変数操作を正しく行うために不可欠な知識です。

関連記事

練習問題

練習問題

%変数%!変数! の展開タイミングについて正しい説明はどれですか?

回答がサーバーに送信されることはありません
#コマンドプロンプト #バッチファイル #遅延展開 #変数 #enabledelayedexpansion