2009-12-03 20:51:48 +0000 2009-12-03 20:51:48 +0000
72
72

バッチファイル内の変数がIF内で設定されない?

非常にシンプルなバッチファイルの例を2つ挙げてみました。

変数に値を代入する。

@echo off
set FOO=1
echo FOO: %FOO%
pause
echo on

これは予想通りの結果になります。

FOO: 1 
Press any key to continue . . .

しかし、同じ2行をIF NOT DEFINEDブロック内に配置した場合:

@echo off
IF NOT DEFINED BAR (
    set FOO=1
    echo FOO: %FOO%
)
pause
echo on

となります。

FOO: 
Press any key to continue . . .

これはIFとは何の関係もないはずですが、明らかにブロックが実行されています。if の上に BAR を定義すると、期待通り PAUSE コマンドのテキストのみが表示されます。

どうしたの?


質問の続きです。setlocalを使わずに遅延拡張を有効にする方法はありますか?

この単純な例のバッチファイルを別のバッチファイルの中から呼び出すと、この例ではFOOが設定されていますが、ローカルにしか設定されていません。

testcaller.bat

@call test.bat 
@echo FOO: %FOO% 
@pause

test.bat @setlocal EnableDelayedExpansion @IF NOT DEFINED BAR ( @set FOO=1 @echo FOO: !FOO! )

と表示されます。

FOO: 1 
FOO: 
Press any key to continue . . .

この場合、どうやらCALLERで遅延拡張を有効にしなければならないようで、面倒くさいかもしれません。

回答 (6)

86
86
86
2009-12-03 21:35:26 +0000

バッチファイル内の環境変数は、行が解析されると展開されます。括弧で区切られたブロックの場合 (if defined のように)、ブロック全体が “行” またはコマンドとしてカウントされます。

これは、ブロックが実行される前に、%FOO%がすべてその値で置き換えられることを意味します。あなたの場合は、変数がまだ値を持っていないので、何もありません。

setlocal enabledelayedexpansion

遅延展開を有効にすると、感嘆符 (!) で区切られた変数が実行時に解析ではなく評価されます。

if not defined BAR (
    set FOO=1
    echo Foo: !FOO!
)

help set の詳細はこちらも参照してください:

最後に、遅延環境変数の展開のサポートが追加されました。このサポートはデフォルトでは常に無効になっていますが、/VのコマンドラインスイッチでCMD.EXECMD /?にすることで有効/無効にすることができます。%VAR%

遅延環境変数の展開は、実行時ではなくテキストの行が読み込まれた時に起こる現在の展開の制限を回避するのに便利です。

set VAR=before
if "%VAR%" == "before" (
set VAR=after
if "%VAR%" == "after" @echo If you see this, it worked
)

はメッセージを表示しません。なぜなら、both IF ステートメントの IF は、最初の IF ステートメントが読み込まれたときに置換されるからです。つまり、複合文の中のIFは、"before “と "after "を比較していることになり、決して等しくはなりません。同様に、次の例は期待通りには動作しません:

set LIST=
for %i in (*) do set LIST=%LIST% %i
echo %LIST%

これは、カレントディレクトリ内のファイルのリストを構築するのではなく、代わりにLIST変数に最後に見つかったファイルを設定するというものです。繰り返しになりますが、これは%LIST%文が読み込まれた時にFORが一度だけ展開され、その時にはLIST変数は空になるからです。

for %i in (*) do set LIST= %i

これは、LISTを最後に見つかったファイルに設定し続けているだけです。

遅延環境変数の展開では、実行時に別の文字(感嘆符)を使用して環境変数を展開することができます。遅延環境変数展開を有効にすると、上記の例では以下のように書くことで意図した通りに動作するようになります。

set VAR=before
if "%VAR%" == "before" (
set VAR=after
if "!VAR!" == "after" @echo If you see this, it worked
)

set LIST=
for %i in (*) do set LIST=!LIST! %i
echo %LIST%
4
4
4
2011-03-26 16:26:14 +0000

同じ動作は、コマンドが一行に並んでいる場合にも起こります(&がコマンドの区切り文字です):

if not defined BAR set FOO=1& echo FOO: %FOO%

ジョーイの説明は私のお気に入りです。ただし、enabledelayedexpansionはWindows NT 4.0では動作しないことに注意してください(Windows 2000についてはわかりません)。

ご質問の件ですが、EnableDelayedExpansionがないとsetlocalはできません。しかし、あなたに逆らっていた元の動作を使って、2つ目の問題を解決することができます:トリックは、必要な変数の値を再び設定する同じ行でendlocalをすることです。

ここにtest.batを修正したものがあります。

@echo off
setlocal EnableDelayedExpansion 
IF NOT DEFINED BAR ( 
    set FOO=1 
    echo FOO: !FOO! 
)
endlocal & set FOO=%FOO%

しかし、この問題のもう一つの回避策があります:インラインブロックや外部ファイルではなく、同じファイル内のプロシージャを使用します。

@echo off
if not defined BAR call :NotDefined
pause
goto :EOF

:NotDefined
set FOO=1
echo FOO: %FOO%
goto :EOF
``` 0x1&
3
3
3
2009-12-03 21:02:01 +0000

そのように動作しない場合、環境変数の展開が遅延している可能性があります。cmd /V:OFF でオフにするか、if の中で感嘆符を使ってください。

@echo off
IF NOT DEFINED BAR (
    set FOO=1
    echo FOO: !FOO!
)
pause
echo on
1
1
1
2013-11-27 11:52:14 +0000

これは、FORコマンドのある行が一度しか評価されないために起こります。再評価する方法が必要です。CALL コマンドで遅延展開をシミュレートすることができます。

for /l %%I in (0,1,5) do call echo %%RANDOM%%
0
0
0
2016-12-28 21:46:34 +0000

時々、私の場合のように、古いNT4サーバのようなレガシーシステムとの互換性が必要な場合、遅延拡張が動作していないか、何らかの理由で動作していない場合は、別の解決策が必要になるかもしれません。

遅延拡張を使用する場合は、少なくともチェックインバッチを含めることをお勧めします。

IF NOT %Comspec%==!Comspec! ECHO Delayed Expansion is NOT enabled.

より読みやすくシンプルなアプローチをお望みの場合は、以下の代替ソリューションをご利用ください。

REM Option 2: use `call` inside block statement
IF NOT DEFINED BAR2 ( 
  set FOO2=2 
  call echo FOO2: %%FOO2%%
)
echo FOO2: %FOO2%

このように動作します。

>REM Option 2: use `call` inside block statement

>IF NOT DEFINED BAR2 (
 set FOO2=2
 call echo FOO2: %FOO2%
)
FOO2: 2

>echo FOO2: 2
FOO2: 2

そして、もう一つの純粋なcmdソリューションです。

REM Option 3: use `goto` logic blocks
IF DEFINED BAR3 goto endbar3
  set FOO3=3 
  echo FOO3: %FOO3%
:endbar3
echo FOO3: %FOO3%

これは次のように動作します。

>REM Option 3: use `goto` logic blocks

>IF DEFINED BAR3 goto endbar3

>set FOO3=3

>echo FOO3: 3
FOO3: 3

>echo FOO3: 3
FOO3: 3

そして、これは完全な IF THEN ELSE 構文ソリューションです。

REM Full IF THEN ELSE solution
IF NOT DEFINED BAR4 goto elsebar4
REM this is TRUE condition, normal batch flow
  set FOO4=6
  echo FOO4: %FOO4%
  goto endbar4
:elsebar4
  set FOO4=4
  echo FOO4: %FOO4%
  set BAR4=4
:endbar4
echo FOO4: %FOO4%
echo BAR4: %BAR4%

は次のように動作します。

>REM Full IF THEN ELSE solution

>IF NOT DEFINED BAR4 goto elsebar4

>set FOO4=4

>echo FOO4: 4
FOO4: 4

>set BAR4=4

>echo FOO4: 4
FOO4: 4

>echo BAR4: 4
BAR4: 4
0
0
0
2012-06-26 15:36:32 +0000

注意すべき点は、同じ行の単一のコマンドであれば動作するということです。

if not defined BAR set FOO=1

とすると、実際にはFOOを1に設定することになります。

if not defined BAR echo FOO: %FOO%

といったように、別のチェックを行うことができます。しかし、setlocal や遅延拡張ビジネスに手を出したくないのであれば、これが手っ取り早い回避策です。