Le variables nel file batch non vengono impostate quando all'interno di IF?

Ho due esempi di file batch molto semplici:

Assegnazione di un valore a una variabile:

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

Quale, come previsto, si traduce in:

 FOO: 1 Press any key to continue . . . 

Tuttavia, se posiziono le stesse due righe all'interno di un block IF NOT DEFINED:

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

Ciò implica inaspettatamente :

 FOO: Press any key to continue . . . 

Questo non dovrebbe avere niente a che fare con l'IF, chiaramente il block viene eseguito. Se definisco BAR sopra il se, viene visualizzato solo il text dal command PAUSE, come previsto.

Che cosa dà?


Seguire la domanda: Esiste un modo per abilitare l'espansione ritardata senza setlocal?

Se wheressi call questo semplice esempio di file batch da dentro l'altro, l'esempio imposta il FOO, ma solo LOCALMENTE.

Per esempio:

testcaller.bat

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

test.bat

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

Ciò visualizza:

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

In questo caso, sembra che devo triggersre un'espansione ritardata nel CALLER, che può essere un problema.

Le variables di ambiente nei file batch vengono espanse quando viene analizzata una row. Nel caso di blocchi delimitati da parentesi (come if defined ) l'integer block conta come una "row" o un command.

Ciò significa che tutte le occorrenze di% FOO% sono sostituite dai loro valori prima che il block sia eseguito. Nel tuo caso con nulla, poiché la variabile non ha ancora un valore.

Per risolvere questo problema è ansible abilitare l'espansione ritardata :

 setlocal enabledelayedexpansion 

L'espansione ritardata causa variables delimitate da esclamazioni ( ! ) Da valutare in esecuzione anziché da analisi che garantiranno il comportmento corretto nel tuo caso:

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

help set dettagli:

Infine, è stato aggiunto il supporto per l'espansione variabile variabile in ritardo. Questo supporto è sempre disabilitato per impostazione predefinita, ma può essere abilitato / disabilitato tramite l'interruttore di linea di command /V a CMD.EXE . Vedi CMD /?

L'espansione della variabile di ambiente ritardata è utile per aggirare le limitazioni dell'espansione corrente che avviene quando viene letto una linea di text, non quando viene eseguita. L'esempio seguente illustra il problema con l'espansione variabile immediata:

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

non avrebbe mai visualizzato il messaggio, poiché il %VAR% in entrambe le istruzioni IF viene sostituito quando viene letto la prima istruzione IF, poiché include logicamente il corpo IF , che è una dichiarazione composta. Quindi l'IF all'interno della dichiarazione composta è veramente confrontando "prima" con "dopo", che non sarà mai uguale. Analogamente, l'esempio riportto di seguito non funziona come previsto:

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

in quanto non creerà un elenco di file nella directory corrente, ma invece imposterà la variabile LIST all'ultimo file trovato. Ancora una volta, è perché il %LIST% viene espanso una sola volta quando viene letto l'istruzione FOR , e in quel momento la variabile LIST è vuota. Quindi il ciclo reale FOR che stiamo eseguendo è:

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

che continua a impostare LIST sull'ultimo file trovato.

L'espansione variabile a tempo variabile consente di utilizzare un carattere diverso (il punto esclamativo) per espandere le variables di ambiente al momento dell'esecuzione. Se l'espansione variabile ritardata è triggersta, gli esempi precedenti potrebbero essere scritti come segue per lavorare come previsto:

 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% 

Se non funziona in questo modo, è probabile che l'espansione variabile dell'ambiente sia ritardata. Puoi distriggersrla con cmd /V:OFF o utilizzare esclamazioni all'interno del tuo se:

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

Lo stesso comportmento si verifica anche quando i comandi sono su una singola row ( & è il command separatore):

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

La spiegazione di Joey è la mia preferita. Notare comunque che enabledelayedexpansion non funziona in Windows NT 4.0 (e non sono sicuro di Windows 2000).

Sulla tua richiesta di follow-up, no, non è ansible EnableDelayedExpansion senza setlocal . Tuttavia il comportmento originale che stava contro di te può essere utilizzato per workaroud che il secondo problema: il trucco è quello di endlocal sulla stessa row in cui impostare nuovamente i valori delle variables che è necessario.

Ecco il tuo test.bat modificato:

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

Ma qui è un'altra soluzione di questo problema: utilizzare una procedura nello stesso file anziché un block in linea o un file esterno.

 @echo off if not defined BAR call :NotDefined pause goto :EOF :NotDefined set FOO=1 echo FOO: %FOO% goto :EOF 

Ciò accade perché la tua linea con il command FOR valutata una sola volta. Hai bisogno di qualche modo per rivalutare. È ansible simulare un'espansione ritardata con il command CALL :

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

Vale la pena notare che funziona se è un singolo command sulla stessa linea.

per esempio

  if not defined BAR set FOO=1 

sarà effettivamente impostato FOO a 1. Si potrebbe quindi fare un altro controllo come:

  if not defined BAR echo FOO: %FOO% 

Ehi, non ho mai detto che fosse bella. Ma se non si vuole confondere con setlocal o il business di espansione ritardata, è una soluzione rapida.

A volte, come ho avuto nel mio caso, quando hai bisogno di essere compatibile con sisthemes legacy come i vecchi server NT4 in cui l'espansione ritardata non funziona o per qualche motivo non funziona potrebbe essere necessaria una soluzione alternativa.

Nel caso in cui utilizzerai Delayd Expansion, consigliamo inoltre di includere alless il check in batch:

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

Nel caso in cui vogliate semplicemente un approccio leggibile e semplice, qui ci sono soluzioni alternative:

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

che funziona così:

 >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 

e una sola soluzione pura cmd:

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

funziona così:

 >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 

e questo è completo se poi la soluzione di sintesi 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% 

funziona così:

 >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