バッチスクリプトの for /R 内で変数を書き替えながら処理をする

 if やfor 内での変数展開は、通常の変数展開とは書式が異なるらしい。

 下記のような処理は失敗します。

@echo off
setlocal
SET imagedir=%USERPROFILE%\My Documents\My Pictures
SET /A count=0
for /R "%imagedir%" %%f in ("*.bmp") do (
	set /A count=count+1
	echo カウント: %count%
)
echo 合計: %count%
カウント: 0
カウント: 0
カウント: 0
カウント: 0
カウント: 0
合計: 5

 期待通りの動作を得るには、下記のようにする。

@echo off
setlocal ENABLEDELAYEDEXPANSION
SET imagedir=%USERPROFILE%\My Documents\My Pictures
SET /A count=0
for /R "%imagedir%" %%f in ("*.bmp") do (
	set /A count=count+1

	echo カウント: !count!

)
echo 合計: %count%
カウント: 1
カウント: 2
カウント: 3
カウント: 4
カウント: 5
合計: 5

 環境変数の他に遅延環境変数というのがあるらしく、!count! のように % ではなく ! を利用する。

 詳しい理由はマニュアル(あるのかどうか知らん)か他サイトを見て欲しい。

ちなみに、以下のような書き方は、分かりにくいが、やはりダメ。

>test.bat & if %ERRORLEVEL% neq 0 echo fail
>test.bat & echo %ERRORLEVEL%

なぜなら、環境変数その行全体の実行開始前に展開されるから。
つまり実行前に%ERRORLEVEL%の値が0だったとすると、以下のように展開されてから実行されることになる。

>test.bat & if 0 neq 0 echo fail
>test.bat & echo 0

したがって、test.batがどんな値を返して(%ERRORLEVEL%にセットされて)も、実行前の値で「&」以降が処理されるということになる。
(この為に、エラーコードチェックに関してはERRORLEVELという演算子がわざわざ用意されているわけだ。これなら値の取得自体はif文の実行時に行うわけだから。)


これはその他の環境変数を使う場面でも同様に起こり得る。特にif文for文ではよく勘違いしてやってしまう。「help for」「for /?」でもわざわざ例示されているくらいだ。

>set var=aaa
>set var=bbb & echo %var%		→「set var=bbb & echo aaa」と展開されてから実行される
aaa
>echo %var%
bbb				→「set var=bbb」が実行されなかったわけではないので、後から見ればちゃんとセットされている
set var=1
if %var% == 1 (			→「if 1 == 1 (
 set/a var=%var% + 2		   set/a var=1 + 2

 echo %var%			   echo 1
)				  )」と展開されてから実行される
set list=
for %%i in (aa bb) do @set list=%list%;%%i	→「for %i in (aa bb) do @set list=;%i」と展開されてから処理されるので、
echo %list%				 「@set list=;aa」「@set list=;bb」が実行される

なお、この問題を解消する為に遅延環境変数というものもある(遅延環境変数有効にしないと使えないが)。

>set var=123
>set var=234 & echo 通常:%var% 遅延:!var!
通常:123 遅延:!var!

>cmd /v:on					…遅延環境変数を有効にして新しいコマンドプロンプトを起動

>set var=123
>set var=234 & echo 通常:%var% 遅延:!var!
通常:123 遅延:234

>exit						…元のコマンドプロンプトへ戻る