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

Variabelen in batch bestand worden niet ingesteld in IF?

Ik heb twee voorbeelden van zeer eenvoudige batch-bestanden:

Een waarde aan een variabele toewijzen:

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

Wat, zoals verwacht, resulteert in:

FOO: 1 
Press any key to continue . . .

Echter, als ik dezelfde twee regels binnen een IF NOT DEFINED blok plaats:

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

Dit onverwacht resulteert in:

FOO: 
Press any key to continue . . .

Dit hoeft niets met de IF te maken te hebben, het blok wordt duidelijk uitgevoerd. Als ik BAR boven de if definieer, wordt alleen de tekst van het PAUZE-commando weergegeven, zoals verwacht.

Wat geeft dat?

  • *

Vervolgvraag: Is er een manier om vertraagde uitbreiding zonder setlocal in te schakelen?

Als ik dit eenvoudige voorbeeldbatchbestand vanuit een ander aanroep, stelt het voorbeeld FOO in, maar alleen LOKAAL.

Bijvoorbeeld:

testcaller.bat

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

test.bat

Dit laat zien:

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

In dit geval lijkt het erop dat ik vertraagde uitbreiding in de CALLER moet inschakelen, wat een gedoe kan zijn.

Antwoorden (6)

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

Omgevingsvariabelen in batchbestanden worden uitgebreid wanneer een regel wordt geparsed. In het geval van blokken die worden begrensd door haakjes (zoals uw if defined) telt het hele blok als een “regel” of commando.

Dit betekent dat alle voorkomens van %FOO% worden vervangen door hun waarden voordat het blok wordt uitgevoerd. In jouw geval door niets, omdat de variabele nog geen waarde heeft.

Om dit op te lossen kun je vertraagde uitbreiding aanzetten:

setlocal enabledelayedexpansion

Vertraagde uitbreiding zorgt ervoor dat variabelen die worden afgebakend door uitroeptekens (!) worden geëvalueerd bij de uitvoering in plaats van bij het parsen, wat in jouw geval voor het juiste gedrag zal zorgen:

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

help set details dit ook:

Tenslotte is ondersteuning voor vertraagde uitbreiding van omgevingsvariabelen toegevoegd. Deze ondersteuning is standaard altijd uitgeschakeld, maar kan worden in-/uitgeschakeld via de /V commandoregel-schakelaar naar CMD.EXE. Zie CMD /?

Vertraagde uitbreiding van omgevingsvariabele is nuttig om de beperkingen van de huidige uitbreiding te omzeilen, die gebeurt wanneer een regel tekst wordt gelezen, niet wanneer deze wordt uitgevoerd. Het volgende voorbeeld demonstreert het probleem met onmiddellijke variabele uitbreiding:

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

zou het bericht nooit weergeven, omdat de %VAR% in beide IF statements wordt vervangen wanneer het eerste IF statement wordt gelezen, omdat het logischerwijs de body van de IF bevat, wat een samengesteld statement is. Dus de IF in het samengestelde statement vergelijkt eigenlijk “voor” met “na”, die nooit gelijk zullen zijn. Evenzo zal het volgende voorbeeld niet werken zoals verwacht:

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

omdat het niet een lijst van bestanden in de huidige directory zal opbouwen, maar in plaats daarvan alleen de LIST variabele zal instellen op het laatst gevonden bestand. Nogmaals, dit komt omdat de %LIST% slechts eenmaal wordt uitgebreid wanneer het FOR statement wordt gelezen, en op dat moment is de LIST variabele leeg. Dus de eigenlijke FOR-lus die we uitvoeren is:

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

die gewoon LIST blijft instellen op het laatst gevonden bestand.

Uitgestelde uitbreiding van omgevingsvariabele staat u toe om een ander karakter te gebruiken (het uitroepteken) om omgevingsvariabelen uit te breiden op uitvoeringstijd. Als vertraagde uitbreiding van variabelen is ingeschakeld, kunnen de bovenstaande voorbeelden als volgt worden geschreven om te werken zoals bedoeld:

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

Hetzelfde gedrag treedt ook op als de commando’s op een enkele regel staan (& is het opdrachtscheidingsteken):

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

Joey’s uitleg is mijn favoriet. Merk echter op dat enabledelayedexpansion niet werkt onder Windows NT 4.0 (en ik ben niet zeker van Windows 2000).

Over je vervolgvraag, nee, het is niet mogelijk om EnableDelayedExpansion zonder setlocal te doen. Het oorspronkelijke gedrag dat je tegenwerkte kan echter wel gebruikt worden om dat tweede probleem op te lossen: de truc is om endlocal op dezelfde regel te zetten waar je opnieuw de waarden instelt van de variabelen die je nodig hebt.

Hier is je test.bat aangepast:

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

Maar hier is een andere workaround voor dat probleem: gebruik een procedure in hetzelfde bestand in plaats van een inline blok of een extern bestand.

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

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

Als het niet op die manier werkt, heb je waarschijnlijk vertraagde omgevingsvariabele uitbreiding aan staan. Je kan het ofwel uitschakelen met cmd /V:OFF of uitroeptekens gebruiken in je 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

Dit gebeurt omdat je regel met het FOR commando maar één keer evalueert. Je hebt een manier nodig om het opnieuw te evalueren. Je zou een vertraagde uitbreiding kunnen simuleren met het commando CALL:

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

Soms, zoals in mijn geval, wanneer je compatibel moet zijn met oudere systemen zoals oude NT4 servers waar vertraagde expansie niet werkt of om de een of andere reden niet werkt, kan je een alternatieve oplossing nodig hebben.

In het geval dat je vertraagde expansie gebruikt, is het ook aan te raden om tenminste een controle in batch op te nemen:

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

In het geval dat je gewoon een meer leesbare en eenvoudige aanpak wilt, zijn hier alternatieve oplossingen:

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

die werkt als volgt:

>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

en nog een pure cmd oplossing:

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

die werkt als volgt:

>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

en dit is de volledige IF THEN ELSE syntaxis oplossing:

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%

het werkt als volgt:

>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

Het is de moeite waard om op te merken dat het wel werkt als het een enkel commando op dezelfde regel is.

b.v.

if not defined BAR set FOO=1

zal in feite FOO op 1 zetten. Je zou dan nog een controle kunnen doen zoals:

if not defined BAR echo FOO: %FOO%

Hé, ik heb nooit gezegd dat het mooi was. Maar als je niet wilt knoeien met setlocal of het vertraagde uitbreidingsgedoe, is het een snelle workaround.