web-dev-qa-db-ja.com

IF内でバッチファイル内の変数が設定されていませんか。

非常に単純なバッチファイルの例が2つあります。

変数に値を代入する:

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

予想どおり、これは次のようになります。

FOO: 1 
Press any key to continue . . .

ただし、IF NOT DEFINEDブロック内に同じ2行を配置したとします。

@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が設定されますが、LOCALLYのみが設定されます。

例えば:

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で遅延拡張を有効にする必要があるように思われますが、これは面倒なことかもしれません。

67
Brown

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

これは、%FOO%のすべての出現箇所が、そのブロックが実行されるbeforeの値に置き換えられることを意味します。あなたのケースでは何もありません、変数にはまだ値がありません。

これを解決するには、delayed expansionを有効にします。

setlocal enabledelayedexpansion

遅延展開により、感嘆符(!)で区切られた変数は、解析ではなく実行時に評価されます。これにより、正しい動作が保証されます。

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

help setでもこれについて詳しく説明します。

最後に、遅延環境変数の拡張のサポートが追加されました。このサポートは常にデフォルトで無効になっていますが、/VコマンドラインをCMD.EXEに切り替えることで有効/無効にすることができます。 CMD /?を参照してください。

遅延環境変数の展開は、テキストの行が実行されたときではなく読み込まれたときに発生する現在の展開の制限を回避するのに役立ちます。次の例は、即時変数展開の問題を示しています。

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

最初のIFステートメントの読み取り時にbothIFステートメントの%VAR%が置換されるため、メッセージは表示されません。これは、論理的にIFの本体(複合ステートメント)が含まれるためです。そのため、複合文内のIFは、実際には「前」と「後」を比較していますが、これは等しくはありません。同様に、次の例は期待通りに動作しません。

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

つまり、現在のディレクトリにnotというファイルのリストが作成されますが、代わりにLIST変数が最後に見つかったファイルに設定されます。繰り返しますが、これは、FORステートメントが読み取られるときに%LIST%が一度だけ展開され、その時点でLIST変数が空であるためです。実際に実行しているFORループは次のようになります。

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%
81
Joey

同じ動作は、コマンドが単一行にある場合にも起こります(&はコマンド区切り文字です)。

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
4
dolmen

それでもうまくいかない場合は、環境変数の展開を遅らせた可能性があります。 cmd /V:OFFで無効にするか、ifの内側に感嘆符を使用することができます。

@echo off
IF NOT DEFINED BAR (
    set FOO=1
    echo FOO: !FOO!
)
pause
echo on
3
John T

これは、FORコマンドを使用した行が1回だけ評価されるためです。再評価するには何らかの方法が必要です。 CALLコマンドで遅延展開をシミュレートできます。

for /l %%I in (0,1,5) do call echo %%RANDOM%%
1
Free Consulting

それが同じ行の単一のコマンドであるなら、それがうまくいくことは注目に値します。

例えば.

   if not defined BAR set FOO=1

実際にはFOOを1に設定します。その後、次のような別のチェックを行うことができます。

   if not defined BAR echo FOO: %FOO%

こんにちは、私はそれがきれいだとは言いませんでした。しかし、setlocalや遅延拡張ビジネスを台無しにしたくないのであれば、それは簡単な回避策です。

0
demoncodemonkey

私の場合と同じように、遅延拡張が機能していない、または何らかの理由で機能していない古いNT4サーバーのようなレガシーシステムとの互換性が必要な場合は、代替ソリューションが必要な場合があります。

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

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