遅延展開
バッチファイルで FOR ループや IF ブロックの中で変数の値を更新したのに、echo で表示すると古い値が表示される──。これはバッチファイルを書く上で非常によくある問題です。
この問題を解決するのが遅延環境変数展開(Delayed Expansion)です。
この記事では、なぜ通常の変数展開では問題が起きるのかを説明し、遅延展開の使い方を具体的なサンプルコードとともに解説します。
問題:ブロック内で変数が更新されない
まず、以下のバッチファイルを見てみましょう。
@echo off
setlocal
set COUNT=0
for %%f in (*.txt) do (
set /a COUNT+=1
echo %COUNT% 個目: %%f
)
echo 合計: %COUNT% 個
endlocal
このバッチファイルでは、テキストファイルの数をカウントしようとしています。しかし、実行結果は期待通りになりません。
COUNT の値が常に 0 のまま表示されています。
原因:変数の展開タイミング
バッチファイルでは、括弧 () で囲まれたブロックは、実行前にまとめて解析されます。このとき %変数% はブロック全体が実行される前の値に展開されます。
つまり、上記の例では for ループに入る前の COUNT の値(0)が %COUNT% に展開され、ループ中に値を更新しても反映されないのです。
rem ブロック全体が解析される時点で %COUNT% は 0 に展開される
for %%f in (*.txt) do (
set /a COUNT+=1
echo 0 個目: %%f ← %COUNT% が 0 に置き換わっている
)
解決方法:遅延展開を有効にする
遅延展開を有効にし、%変数% の代わりに !変数! を使用することで、変数が実行時に展開されるようになります。
手順
setlocal enabledelayedexpansionを記述する- ブロック内の変数参照を
!変数名!に変更する
@echo off
setlocal enabledelayedexpansion
set COUNT=0
for %%f in (*.txt) do (
set /a COUNT+=1
echo !COUNT! 個目: %%f
)
echo 合計: !COUNT! 個
endlocal
今度は 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!
)
遅延展開が必要な典型例
ファイル名に連番を付ける
@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
ファイルの内容を変数に蓄積する
@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 ブロックの中 |
| 注意点 | ! を含む文字列が誤って解釈される |
遅延展開は、バッチファイルでループやブロック内の変数操作を正しく行うために不可欠な知識です。