バッチファイルのfor・if文の中で変数使う際につまづくこと

コマンドプロンプトとはWindowsでの黒い画面のことですが、その中でスクリプト(プログラム)を記述することができます。下記の質問によるとUNIXシェルスクリプトとは違うようで~言語という呼称はないようです。

Windowsの.batファイルのプログラムは何言語?? - その他(プログラミング) - 教えて!goo

何か他のプログラム言語をかじった人が同じように、バッチファイルの中でfor・if文を使うと、動作が想像と違いつまづくかと思います。私は複文、ブロック文の中で変数を使用した際につまづきました。

コマンドプロンプトでブロック文を使う際には、下記のように記述します。他の言語と違うところは、閉じカッコは行頭に置くことです。

for %%i in (*) do (  
)

if %i% == 1 (  
)

繰り返しの中で変数を使用する場合は、毎回変数の中身が変わると思うのですが、コマンドプロンプトではそうはいきません。forの中で変数iをインクリメントしながら環境変数xに格納します。そしてechoで表示しましょう。しかし、下記のように書くと、何故かすべて3が表示されてしまいます。

@echo off
for /L %%i in (1,1,3) do (
    set x=%%i
    echo %x%
)
echo for終了時 x:%x%
pause
ECHO は <OFF> です。
ECHO は <OFF> です。
ECHO は <OFF> です。
for終了時 x:3

何故か、環境変数xの中身が表示されません。for文の変数iは中身がないのでしょうか。確かめてみましょう。

@echo off
for /L %%i in (1,1,3) do (
    echo %%i
)
pause
1
2
3

変数iをechoで表示してみると、ちゃんとインクリメントされていることがわかります。問題は環境変数xにありそうです。この問題が下記の記事で解説されていました。

バッチファイルのif文やfor文で気をつけること

実はfor文、if文の中の環境変数は処理が終了後に格納した値が反映されます。これを環境変数の即時展開と言います。

下記でざっくりと説明されていますので、一読して整理しましょう。

CMD.EXEの遅延環境変数の展開 - ふなWiki

さきほどの1~3を表示するソースを下記のように書き換えてみましょう。

@echo off
setlocal enabledelayedexpansion
for /L %%i in (1,1,3) do (
    set x=%%i
    echo !x!
)
echo for終了時 x:%x%
pause
1
2
3

今度は意図したとおりに表示されました。

各コマンドが実行されるときに環境変数が展開されるのではなく、バッチファイルのテキスト行が読み取られるときに展開が行われてしまうからで、上の例は次のように解釈されているために期待した結果が得られないのです。

引用元: ふなWiki

ふなwikiの一文からの推測ですが、テキストを読み取る、つまりifやforの行に移動して、この2つのコマンドを読み取るとき、つまりコマンドの実行前に環境変数は展開されてしまうのだと思います。コマンドは環境変数を先に展開してから実行するのだとすると、最初のソースが何も表示されない理由がわかります。

set x=%%iはまだ実行されていない状態で環境変数xを展開するので中身は空のままだったのです。その後、環境変数に値は格納はされていきますが、すでに展開はされていますので、ブロック文の中では空の中身が表示されることになります。

環境変数についてもう少し知りたい方は下記で学んでおきましょう。

Windows環境変数メモ(Hishidama's Windows Environment Memo)