LoginSignup
2
4

More than 3 years have passed since last update.

バッチファイルのif文やfor文の中で、変数の値がおかしい問題

Last updated at Posted at 2021-04-11

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%形式で取得

delayedexpansion_if1_ok.bat
@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回目は成功する(ように見える)

「できる場合とできない場合がある。理由はわからない」って最悪じゃないですか。
それです。

delayedexpansion_if2_ng.bat
@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文でも発生します。

delayedexpansion_for2_ng.bat
@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回目の結果としては誤っている可能性があります)。

直前に格納した値を出力したければ、以下のようにするべきでした。

delayedexpansion_if2_ok.bat
@echo off
rem 遅延環境変数の展開を有効にする
setlocal enabledelayedexpansion

if 1==1 (
  set str_a=aaa
  echo !str_a!
)
実行結果
> delayedexpansion_if2_ok.bat
aaa

(3) 失敗例:if文の中で値を格納しても、期待した値でない

こんなこともありました。

delayedexpansion_if3_ng.bat
@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を出力したければ、以下のようにするべきでした。

delayedexpansion_if3_ok.bat
@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文がネストされている場合はどうなるんでしょう?
試してみました。

delayedexpansion_if4.bat
@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

2
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
4