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

Variablen in Batch-Datei werden nicht gesetzt, wenn sie innerhalb von IF?

Ich habe zwei Beispiele für sehr einfache Batch-Dateien:

Zuweisung eines Wertes an eine Variable:

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

Was erwartungsgemäß folgendes ergibt:

FOO: 1 
Press any key to continue . . .

Wenn ich jedoch dieselben beiden Zeilen innerhalb eines IF NOT DEFINED-Blocks platziere:

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

Das ergibt unerwarteterweise:

FOO: 
Press any key to continue . . .

Dies sollte nichts mit dem IF zu tun haben, offensichtlich wird der Block ausgeführt. Wenn ich BAR oberhalb des if definiere, wird nur der Text des PAUSE-Befehls angezeigt, wie erwartet.

Was ergibt das?


Folgefrage: Gibt es eine Möglichkeit, die verzögerte Expansion ohne setlocal zu aktivieren?

Wenn ich diese einfache Beispiel-Batchdatei aus einer anderen heraus aufrufe, setzt das Beispiel FOO, aber nur LOKAL.

Zum Beispiel:

testcaller.bat

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

test.bat

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

Dies zeigt:

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

In diesem Fall scheint es, dass ich die verzögerte Expansion im CALLER aktivieren muss, was ein Problem sein kann.

Antworten (6)

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

Umgebungsvariablen in Batch-Dateien werden beim Parsen einer Zeile expandiert. Bei Blöcken, die durch Klammern begrenzt sind (wie Ihr if defined), zählt der gesamte Block als eine “Zeile” oder ein Befehl.

Das bedeutet, dass alle Vorkommen von %FOO% durch ihre Werte ersetzt werden, bevor der Block ausgeführt wird. In Ihrem Fall durch nichts, da die Variable noch keinen Wert hat.

Um dies zu lösen, können Sie die verzögerte Expansion aktivieren:

setlocal enabledelayedexpansion

Die verzögerte Expansion bewirkt, dass Variablen, die durch Ausrufezeichen (!) begrenzt sind, bei der Ausführung ausgewertet werden, anstatt geparst zu werden, was in Ihrem Fall das richtige Verhalten sicherstellt:

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

help set Details dazu:

Schließlich wurde die Unterstützung für verzögerte Expansion von Umgebungsvariablen hinzugefügt. Diese Unterstützung ist standardmäßig immer deaktiviert, kann aber über den Befehlszeilenschalter /V auf CMD.EXE aktiviert/deaktiviert werden. Siehe CMD /?

Die verzögerte Umgebungsvariablenexpansion ist nützlich, um die Einschränkungen der aktuellen Expansion zu umgehen, die beim Lesen einer Textzeile und nicht bei deren Ausführung erfolgt. Das folgende Beispiel demonstriert das Problem mit der sofortigen Variablenexpansion:

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

würde niemals die Meldung anzeigen, da das %VAR% in beiden IF-Anweisungen ersetzt wird, wenn die erste IF-Anweisung gelesen wird, da sie logischerweise den Körper des IF enthält, der eine zusammengesetzte Anweisung ist. Das IF innerhalb der zusammengesetzten Anweisung vergleicht also wirklich “vorher” mit “nachher”, was niemals gleich sein wird. In ähnlicher Weise wird das folgende Beispiel nicht wie erwartet funktionieren:

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

, da es nicht eine Liste der Dateien im aktuellen Verzeichnis aufbaut, sondern stattdessen nur die Variable LIST auf die letzte gefundene Datei setzt. Das liegt wiederum daran, dass die Variable %LIST% nur einmal expandiert wird, wenn die Anweisung FOR gelesen wird, und zu diesem Zeitpunkt ist die Variable LIST leer. Die tatsächliche FOR-Schleife, die wir ausführen, lautet also:

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

, was einfach LIST auf die zuletzt gefundene Datei setzt.

Die verzögerte Umgebungsvariablenexpansion ermöglicht es Ihnen, ein anderes Zeichen (das Ausrufezeichen) zu verwenden, um Umgebungsvariablen zur Ausführungszeit zu expandieren. Wenn die verzögerte Variablenexpansion aktiviert ist, könnten die obigen Beispiele wie folgt geschrieben werden, um wie vorgesehen zu funktionieren:

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
3
3
3
2009-12-03 21:02:01 +0000

Wenn es nicht so funktioniert, haben Sie wahrscheinlich die verzögerte Erweiterung der Umgebungsvariablen eingeschaltet. Sie können sie entweder mit cmd /V:OFF ausschalten oder Ausrufezeichen in Ihrem if verwenden:

@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

Dies geschieht, weil Ihre Zeile mit dem Befehl FOR nur einmal ausgewertet wird. Sie brauchen eine Möglichkeit, sie neu auszuwerten. Sie könnten eine verzögerte Expansion mit dem Befehl CALL simulieren:

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

Manchmal, wie in meinem Fall, wenn Sie mit Altsystemen wie alten NT4-Servern kompatibel sein müssen, bei denen die verzögerte Erweiterung nicht funktioniert oder aus irgendeinem Grund nicht funktioniert, benötigen Sie möglicherweise eine alternative Lösung.

Falls Sie Delayd Expansion verwenden, wird auch empfohlen, dass zumindest Check in Batch enthalten sein sollte:

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

Für den Fall, dass Sie nur einen lesbaren und einfachen Ansatz wollen, hier sind alternative Lösungen:

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

, was wie folgt funktioniert:

>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

und eine weitere reine cmd-Lösung:

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

das funktioniert so:

>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

und dies ist eine vollständige IF THEN ELSE Syntaxlösung:

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%

es funktioniert so:

>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

Es ist erwähnenswert, dass es funktioniert, wenn es ein einzelner Befehl in der gleichen Zeile ist.

z.B.

if not defined BAR set FOO=1

setzt tatsächlich FOO auf 1. Sie könnten dann eine weitere Prüfung durchführen, wie z.B:

if not defined BAR echo FOO: %FOO%

Hey, ich habe nie gesagt, dass es schön ist. Aber wenn Sie sich nicht mit setlocal oder der verzögerten Expansion herumschlagen wollen, ist das ein schneller Workaround.