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


69

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

Einer Variablen einen Wert zuweisen:

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

Was wie erwartet zu Folgendem führt:

FOO: 1 
Press any key to continue . . .

Wenn ich jedoch dieselben zwei Zeilen in einen IF NOT DEFINED-Block einfüge:

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

Dies führt unerwartet zu:

FOO: 
Press any key to continue . . .

Dies sollte nichts mit der IF zu tun haben, da der Block eindeutig ausgeführt wird. Wenn ich BAR über if definiere, wird erwartungsgemäß nur der Text des Befehls PAUSE angezeigt.

Was gibt?


Anschlussfrage: Gibt es eine Möglichkeit, eine verzögerte Erweiterung ohne setlocal zu ermöglichen?

Wenn ich diese einfache Beispiel-Batch-Datei von einer anderen Seite aus aufrufen würde, würde das Beispiel FOO setzen, aber nur LOCALLY.

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 an:

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

In diesem Fall muss ich anscheinend die verzögerte Erweiterung im CALLER aktivieren, was ein Problem sein kann.

Antworten:


84

Umgebungsvariablen in Stapeldateien werden erweitert, wenn eine Zeile analysiert wird. Bei Blöcken, die durch (Ihre if defined) Klammern begrenzt sind , zählt der gesamte Block als "Zeile" oder Befehl.

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

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

setlocal enabledelayedexpansion

Eine verzögerte Erweiterung bewirkt, dass durch Ausrufezeichen ( !) begrenzte Variablen bei der Ausführung ausgewertet werden, anstatt sie zu analysieren, wodurch das richtige Verhalten in Ihrem Fall sichergestellt wird:

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

help set Details dazu auch:

Schließlich wurde die Unterstützung für die verzögerte Erweiterung von Umgebungsvariablen hinzugefügt. Diese Unterstützung ist standardmäßig immer deaktiviert, kann jedoch über die /VBefehlszeilenoption aktiviert / deaktiviert werden CMD.EXE. SehenCMD /?

Die verzögerte Erweiterung von Umgebungsvariablen ist hilfreich, um die Einschränkungen der aktuellen Erweiterung zu umgehen, die beim Lesen einer Textzeile und nicht beim Ausführen auftreten. Das folgende Beispiel zeigt das Problem mit der sofortigen Variablenerweiterung:

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

würde die Nachricht niemals anzeigen, da die Anweisung %VAR%in beiden IF Anweisungen beim Lesen der ersten IF-Anweisung ersetzt wird, da sie logisch den Hauptteil der IFAnweisung enthält , bei der es sich um eine zusammengesetzte Anweisung handelt. Das IF in der zusammengesetzten Anweisung vergleicht also wirklich "Vorher" mit "Nachher", was niemals gleich sein wird. In ähnlicher Weise funktioniert das folgende Beispiel nicht wie erwartet:

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

dadurch wird keine Liste der Dateien im aktuellen Verzeichnis erstellt, sondern lediglich die LIST Variable auf die zuletzt gefundene Datei gesetzt. Dies ist wiederum darauf zurückzuführen, dass das %LIST%Attribut nur einmal erweitert wird, wenn die FOR Anweisung gelesen wird. Zu diesem Zeitpunkt ist die LISTVariable leer. Die eigentliche FOR-Schleife, die wir ausführen, lautet also:

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

das setzt nur LISTauf die zuletzt gefundene Datei.

Mit der verzögerten Erweiterung von Umgebungsvariablen können Sie zur Ausführungszeit Umgebungsvariablen mit einem anderen Zeichen (dem Ausrufezeichen) erweitern. Wenn die verzögerte Variablenerweiterung aktiviert ist, können die obigen Beispiele wie folgt geschrieben werden, um wie beabsichtigt 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%

17
Und wenn Sie ein Echo benötigen !, verwenden Sie ^^^!(es zweimal zu entkommen). Andernfalls wird die "verzögerte Erweiterung" -Funktion es essen.
Grawity

4

Das gleiche Verhalten tritt auch auf, wenn sich die Befehle in einer einzelnen Zeile befinden ( &ist das Befehlstrennzeichen):

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

Joeys Erklärung ist meine Lieblingserklärung. Beachten Sie jedoch, dass enabledelayedexpansiondies unter Windows NT 4.0 nicht funktioniert (und ich bin mir bei Windows 2000 nicht sicher).

Über Ihre Follow-up - Frage, nein, ist es nicht möglich, EnableDelayedExpansionohne setlocal. Das ursprüngliche Verhalten, mit dem Sie konfrontiert wurden, kann jedoch verwendet werden, um dieses zweite Problem zu umgehen: Der Trick besteht darin, endlocalin derselben Zeile die Werte der benötigten Variablen erneut festzulegen.

Hier ist Ihre test.batmodifizierte:

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

Hier ist eine andere Problemumgehung: Verwenden Sie eine Prozedur in derselben Datei anstelle eines Inline-Blocks oder einer externen Datei.

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

:NotDefined
set FOO=1
echo FOO: %FOO%
goto :EOF

"Der Trick ist, auf der gleichen Linie zu endlokalisieren" - DANKE dafür!
Dforce

3

Wenn dies nicht der Fall ist, haben Sie wahrscheinlich die Erweiterung der Umgebungsvariablen verzögert. Sie können es entweder mit ausschalten cmd /V:OFFoder Ausrufezeichen in Ihrem verwenden, wenn:

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

3
Durch Aktivieren der verzögerten Erweiterung wird nur die Semantik des Ausrufezeichens geändert. Es hat keinerlei Auswirkungen auf die normale Variablenexpansion. Außerdem ist es sehr unwahrscheinlich, dass es versehentlich aktiviert wird. Personen, die es aktiviert haben, tun dies normalerweise absichtlich.
Joey

1

Dies geschieht, weil Ihre Zeile mit dem FORBefehl nur einmal ausgewertet wird. Sie brauchen eine Möglichkeit, es neu zu bewerten. Sie können eine verzögerte Erweiterung mit folgendem CALLBefehl simulieren :

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

Die verzögerte Erweiterung funktionierte nicht, aber dies / do call / funktionierte auf win7, damit mein 3-zeiliges Cmd-Skript den echten Hardlink findet: @for / f "delims =" %% a in ('bash ~ / perl / cwd2 .sh% * ') benenne set CWD_REAL = %% ein Echo cd / d% CWD_REAL% cd / d% CWD_REAL%
mosh

0

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

z.B

   if not defined BAR set FOO=1

wird tatsächlich FOOauf 1 gesetzt. Sie könnten dann eine weitere Überprüfung durchführen, z.

   if not defined BAR echo FOO: %FOO%

Hey, ich habe nie gesagt, dass es hübsch ist. Aber wenn Sie sich nicht mit Setlocal oder dem verzögerten Expansionsgeschäft herumschlagen möchten, können Sie dies schnell umgehen.


0

Manchmal, wie ich es in meinem Fall getan habe, benötigen Sie möglicherweise eine alternative Lösung, wenn Sie mit älteren Systemen wie alten NT4-Servern kompatibel sein müssen, auf denen eine verzögerte Erweiterung nicht funktioniert oder aus irgendeinem Grund nicht funktioniert.

Falls Sie die Delayd-Erweiterung verwenden, wird auch empfohlen, mindestens den Check-in-Batch einzuschließen:

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

Für den Fall, dass Sie einfachere und übersichtlichere Vorgehensweisen wünschen, finden Sie hier 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 so 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 noch eine 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%

es 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 das ist voll, wenn dann andere Syntax-Lö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
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.