複合文中での環境変数参照

iffor( ) の内部での % での環境変数置換は最初に行われるため、複合文内部で環境変数の値を変更してもその新しい値を参照しにくい。この参照の仕方を整理しておく。

(1) 遅延環境変数展開を使う
 これが一番簡単であるが、デメリットとしては、% で展開する環境変数や、for制御変数の値の中に ! が含まれているとそれも展開されてしまい、予期しない値になることがある。これが理由で、以前 head コマンドを作成しようとした際、採用できなかった。
例:

setlocal enabledelayedexpansion
for /l %%I in (1,1,10) do (
 set /a X=%%I %% 2
 if !X!==1 ( echo %%I 奇数) else ( echo %%I 偶数)
)

(2) call を使う
 環境変数の直近の値を他の環境変数にセットしたい場合、call set B=%%A%% とすると、その文を実行する時点での A の値が B にセットできる。ただし、iffor には使えないため、call if %%A%%==1 のような直近の値での判断が出来ない。

(3) サブルーチンコールを使う
 call :sub とすると、:sub の中では各環境変数の直近の値が参照できる。ただ、その参照結果をどうやって呼び出し元に伝えるか。これには、ERRRORLEVEL を使う方法と、DEFINED を使う方法が考えられる。
例1:

for /l %%I in (1,1,10) do (
 call :sub %%I
 if ERRORLEVEL 1 ( echo %%I 奇数) else ( echo %%I 偶数)
)
goto :eof
:sub
 set /a X=%1 %% 2
 exit /b %X%

例2:

for /l %%I in (1,1,10) do (
 call :sub %%I Z
 if defined Z ( echo %%I 奇数) else ( echo %%I 偶数)
)
goto :eof
:sub
 set %2=
 set /a X=%1 %% 2
 if %X%==1 set %2=1
 goto :eof

ERRORLEVEL を使うと数字の結果を返すことが出来る。ただし、%ERRORLEVEL% での参照が出来ないので、旧来の if ERRORLEVEL の書式しか使えない。環境変数の定義状態では基本的には2値状態しか返せないし、setlocal してないとスクリプトの外の環境変数が書き換わってしまう。
この機能を使うと、以前断念した、for /f を使った head コマンドが作成できそうだ。

(4) for 環境変数にセットする
 for 環境変数は直近の値が参照できるので、それに環境変数の直近値を代入できれば良い。しかし、以前書いた通り、

call if /f "delims=" %%%%B in ("%%A%%") do echo %%%%B

のようなことは出来ない。%%A%% を再度評価できればいいので、ちょっと工夫して、

for /f "delims=" %%B in ('echo %%A%%') do echo %%B

とすると、' ' の内部はサブシェルのcmd.exeで実行されるため、再度 % の評価が出来、目的を果たすことが出来る。しかも、setlocal しなくても外の環境変数に影響が無いので、何らかの理由(外の環境変数をセットしたい or カレントディレクトリ変更の結果を外に伝えたい)で setlocal を使えない場合にも応用できる。
例:

for /l %%I in (1,1,10) do (
 set /a X=%%I %% 2
 for /f %%Z in ('echo %%X%%') do (
  if %%Z==1 ( echo %%I 奇数) else ( echo %%I 偶数)
 )
)