batファイルでfor文内に変数を利用する場合の罠

プログラミング
スポンサーリンク

はじめに

今回は、batファイルを書いていてハマった問題を紹介したいと思います。

やりたかったことは、batファイルから外部のテキストファイルを読み込み、そのテキストファイルの各行に記載されている文字を、別のバッチファイルへの引数に渡して処理をさせたいと思っていました。

しかし、なぜか別のバッチファイルの引数に文字列が渡らないという問題が発生したため、原因を突き止めていきました。

ファイルをロードして処理するスクリプト

調査

再現するスクリプトを調べた結果、以下のことが分かりました。

読み込みたいテキストファイル(Text.txt)を用意します。

1
2
3
4

上記のファイルを各行ごとに表示するスクリプトを用意。

@echo off

for /f %%a in (./Text.txt) do (
	set line=%%a
	echo %line%
)

pause

上記の処理でechoにより、1,2,3,4が表示されることを期待してたのですが、実行すると以下のようになります。

ECHO は <OFF> です。
ECHO は <OFF> です。
ECHO は <OFF> です。
ECHO は <OFF> です。
続行するには何かキーを押してください . . .

これはおかしいですね。

ECHO は <OFF> です。

なぜか、ECHO は <OFF> です。と表示されています。このような記載はしていないはずなのに不思議ですね。「ECHO は <OFF> です。」という単語を調査した結果、変数自体の初期化ができていないときのエラーのようです。

どうやら、set line=%%a の行自体が実行されず、初期化されていない扱いになっていそうです。

ブロック文の環境変数の展開タイミング

さらに調査を進めた結果、Windowsのバッチファイルの仕様であることが分かりました。具体的には、括弧()をつかったブロック文を作った場合に、このブロックに入った瞬間に、ブロック内の内部の環境変数が全て展開されるようです。

つまり、ブロック内の echo %line% は、その上の行の set line=%%a を実行される前に展開されてしまい、%line%が未初期化のため、エラーになるのです。

修正方法

以下、修正方法として以下の手順の説明をします。

  • そもそもブロック内で環境変数への代入を避ける
  • 遅延環境変数」を利用する

ブロック内で環境変数に代入しない

ブロック内で環境変数に代入し、代入結果を利用することが問題なので、例えば、代入結果を利用しないように以下のようにするだけで修正できます。

@echo off

for /f %%a in (./Text.txt) do (
	echo %%a
)

pause

遅延環境変数を使用する

展開自体を遅らせる方法があります。それは、遅延環境変数を利用するというもの。

使用方法は、変数を使うときに%line%ではなく、!line! のようにエクスクラメーションマークで囲います。さらに遅延環境変数を使用する前に、setlocal EnableDelayedExpansionを実行して、遅延環境変数が利用できるように設定しておく必要があります。

上記をふまえて以下のように書き換えましょう。

@echo off
setlocal EnableDelayedExpansion

for /f %%a in (./Text.txt) do (
	set line=%%a
	echo !line!
)

pause
endlocal

環境変数の展開タイミングによる他の問題例

今回、for文を使用したことで問題が発生しましたが、ブロック文自体が問題なので if文でも同様の問題が発生します。

@echo off

set num=1

if %num% equ 1 (
	echo %num%
	set num=2
	echo %num%
)

echo %num%

pause

「122」と出ることを期待して上記を実行すると

1
1
2
続行するには何かキーを押してください . . .

と出てしまいます。

これもブロック内を展開するタイミングで、echo %num%が全て、echo 1 に展開されてしまうためです。

遅延環境変数で解決できます。

@echo off
setlocal EnableDelayedExpansion

set num=1

if !num! equ 1 (
	echo !num!
	set num=2
	echo !num!
)

echo !num!

pause
endlocal

おわりに

展開するタイミングを考えるのは大変なので、そもそも普通の環境変数は、遅延環境変数と扱ってくれればいいのですけどもね・・・。とりあえずbatファイルなどのスクリプトは、結構知らない仕様がまだまだあって怖いなと思いました。

最後まで読んでいただきありがとうございました。

コメント

タイトルとURLをコピーしました