Y子です。
変数に格納したはずの値が、
・if
文やfor
文の中で取得できなかったり、
・取得できても古い値のままだったり、
という病気にかかりました。
調べたところ、 「遅延環境変数の展開を有効にする」 というおまじないが必要だそうです。
ナニソレー
でもどうやら、ちょっと複雑なバッチファイルを書くためには、避けて通れない技だそうで…。
いろいろ試して、ようやくわかった気になりましたので、なんとかそれをまとめてみます。
【2021/04/12 19:50 追記】
時間がない人のための簡潔スッキリ版も書きました。ご活用ください。
概要
※煩雑になるので「if
文」と書きますが、すべて「if
文やfor
文」と読み替え可能です※
if
文の中で変数に値を格納し、その値を同じif
文の中で取得するには、以下の注意が必要です。
・%a%
では取得不可 (%a%
には、if
文に入る前の値が入っています)
・事前にsetlocal enabledelayedexpansion
を宣言すれば、!a!
で取得可
if
文中の変数%a%
は、if
文に入った瞬間に展開されます。
これに対し、参照されるまで変数の展開を遅延させることを「遅延環境変数の展開」といいます。
これを有効にする宣言が「setlocal enabledelayedexpansion
」で、取得する変数が!a!
です。
コード
(1) 成功例
以下のことを実施しています。
・遅延環境変数の展開を有効にする宣言
・値の「格納と取得」を、同じif
文の中で行う変数は、!a!
形式で取得
・それ以外の変数は、%a%
形式で取得
@echo off
rem 遅延環境変数の展開を有効にする
setlocal enabledelayedexpansion
rem if文の外で格納
set str_a=aaa
if 1==1 (
rem if文の中で格納
set str_b=bbb
echo if文の中で取得 : "%str_a%" "!str_b!"
)
echo if文の外で取得 : "%str_a%" "%str_b%"
値の取得を4回行っていますが、そのうち2回目だけが、「if
文の中で格納した値を、同じif
文の中で取得」したい場所なので、!str_b!
を使います。
> delayedexpansion_if1_ok.bat
if文の中で取得 : "aaa" "bbb"
if文の外で取得 : "aaa" "bbb"
すべて、格納した通りの値が取得できました。
より正確に言うと、上記の例は結果的に、すべて!a!
形式で取得しても、期待した結果になります。
同じif
文の中で値を格納していなければ、%a%
と!a!
どちらで取得しても同じです。
上記の例では、!a!
としなければ期待した(直前で格納した)値を取得できない場所だけ、!a!
形式にした、ということです。
(2) 失敗例:if文の中で値の取得に失敗する。2回目は成功する(ように見える)
「できる場合とできない場合がある。理由はわからない」って最悪じゃないですか。
それです。
@echo off
if 1==1 (
set str_a=aaa
echo %str_a%
)
> delayedexpansion_if2_ng.bat
ECHO は <OFF> です。
なんじゃこりゃ、ってなりません?
「ECHO は <OFF> です。
」は、echo
コマンドを引数なしで実行したときに出るやつです。
つまり、変数%str_a%
が空っぽということです。直前で格納してるのに!
でも、2回実行すると、2回目はaaa
になるんですよ。やんなっちゃいますね。
> delayedexpansion_if2_ng.bat
ECHO は <OFF> です。
> delayedexpansion_if2_ng.bat
aaa
同じ問題は、for
文でも発生します。
@echo off
for /l %%a in (1,1,1) do (
set str_a=aaa
echo %str_a%
)
> delayedexpansion_for2_ng.bat
ECHO は <OFF> です。
これらはいずれも 概要 に書いた通り、if
文に入った瞬間(値を格納する前)の%str_a%
の値が出力されてこうなりました。
直前に格納した値が出力されることを期待したわたしは、悲しい思いをしました。
「2回目は成功する」のは、同じ変数を使いまわした結果1回目に格納された値が取得できただけなので、厳密には成功とは言えません(値が変わる処理だったら、2回目の結果としては誤っている可能性があります)。
直前に格納した値を出力したければ、以下のようにするべきでした。
@echo off
rem 遅延環境変数の展開を有効にする
setlocal enabledelayedexpansion
if 1==1 (
set str_a=aaa
echo !str_a!
)
> delayedexpansion_if2_ok.bat
aaa
(3) 失敗例:if文の中で値を格納しても、期待した値でない
こんなこともありました。
@echo off
set str_a=aaa
if 1==1 (
set str_a=bbb
echo %str_a%
)
echo %str_a%
> delayedexpansion_if3_ng.bat
aaa
bbb
おかしいじゃないですか(半ギレ)。
直前にbbb
を入れてるのに、aaa
なんて言われちゃうんですから。
ん?でもif
文が終わった後はbbb
になってる…
もちろんこれも、if
文の中では、if
文に入った瞬間(値を格納する前)の%str_a%
の値が出力されただけのことです。
if
文が終わると、if
文の中で格納された値が反映されてbbb
が出力されます。
if
文の中でも、直前に格納したbbb
を出力したければ、以下のようにするべきでした。
@echo off
rem 遅延環境変数の展開を有効にする
setlocal enabledelayedexpansion
set str_a=aaa
if 1==1 (
set str_a=bbb
echo !str_a!
)
echo !str_a!
> delayedexpansion_if3_ok.bat
bbb
bbb
(4) 複雑な例:if文の多重化
「if
文に入った瞬間の値」と何回も書きましたが、if
文がネストされている場合はどうなるんでしょう?
試してみました。
@echo off
set str_a=aaa
if 1==1 (
set str_a=bbb
echo %str_a%
if 1==1 (
set str_a=ccc
echo %str_a%
)
echo %str_a%
)
echo %str_a%
aaa
aaa
aaa
ccc
遅延展開をしないまま、if
文の階層が変わるたびに、値の格納と取得をしてみました。
その結果、取得した値が変わったのは、最上位のif
文が終わったときの1回だけでした。
つまり、
・ネストされている場合、親のif
文に入った時点で子if
文の中の変数まで展開される
・それ以降は、子if
文に入ろうが出ようが、親if
文が終わるまで変わらない
ってことみたいです。
おわびに
この記事がわかりやすい自信がありません。
現国の長文読解みたいです。(あははっ)
ではー。(_ _)zzZ