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


69

非常に単純なバッチファイルの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を設定しますが、ローカルのみです。

例えば:

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で遅延拡張を有効にしなければならないようです。これは面倒かもしれません。

回答:


84

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

つまり、%FOO%のすべての出現は、ブロックが実行される前にそれらの値置き換えられます。変数にはまだ値がないので、何もない場合は。

これを解決するには、遅延展開を有効にします。

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ステートメントが読み取られたとき%VAR%両方の IFステートメントが置換されるため、メッセージは表示されませんIF。これは、複合ステートメントであるの本文を論理的に含むためです。したがって、複合ステートメント内のIFは、実際には「前」と「後」を比較しており、決して等しくなることはありません。同様に、次の例は期待どおりに機能しません。

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

それにはなりません、現在のディレクトリ内のファイルのリストを構築し、代わりにちょうど設定されますLIST 最後に見つかったファイルに変数を。繰り返しますが、これは%LIST%FOR ステートメントが読み取られるときに一度だけが展開され、その時点で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%

17
また、をエコーする必要がある場合は!、を使用します^^^!(2回エスケープします)。そうしないと、「遅延拡張」機能がそれを消費します。
grawity

4

同じ動作は、コマンドが1行にある場合にも発生します(&コマンドの区切りです):

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

ジョーイの説明は私のお気に入りです。ただし、enabledelayedexpansionWindows 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

「トリックは同じ行でローカルにエンドすることです」-それをありがとう!
dforce

3

その方法で動作しない場合は、環境変数の展開が遅れている可能性があります。cmd /V:OFFif でオフにするか、感嘆符を使用できます:

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

3
遅延展開を有効にしても、感嘆符の意味が変わるだけです。通常の変数展開には何の影響もありません。また、誤って有効にすることはほとんどありません。通常、それを有効にしている人は、意図的にそれを行います。
ジョーイ

1

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

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

遅延拡張は機能しませんでしたが、3行のcmdスクリプトが実際のハードリンクを見つけるために、この/ do call /はwin7で機能しました:@for / f "delims =" %% a in( 'bash〜/ perl / cwd2 .SH%は、* ')CWD_REAL = %%エコーCD / D%CWD_REAL%CD / D%CWD_REAL%に設定呼ぶのです
モッシュ

0

同じ行の単一のコマンドであれば、機能することは注目に値します。

例えば

   if not defined BAR set FOO=1

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

   if not defined BAR echo FOO: %FOO%

ねえ、私はそれがきれいだと言ったことはありません。しかし、setlocalまたは遅延拡張ビジネスをいじりたくない場合は、簡単な回避策です。


0

私の場合のように、遅延拡張が機能しない、または何らかの理由で機能しない古い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

そしてもう1つの純粋な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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.