it-swarm.dev

Le variabili nel file batch non vengono impostate quando si trova all'interno di IF?

Ho due esempi di file batch molto semplici:

Assegnare 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 inserisco le stesse due righe all'interno di un blocco IF NOT DEFINED:

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

Questo inaspettatamente risulta in:

FOO: 
Press any key to continue . . .

Questo non dovrebbe avere nulla a che fare con l'IF, chiaramente il blocco è in esecuzione. Se definisco BAR sopra il se, viene visualizzato solo il testo del comando PAUSE, come previsto.

Cosa dà?


Domanda successiva: esiste un modo per abilitare l'espansione ritardata senza setlocal?

Se dovessi chiamare questo semplice file batch di esempio dall'interno di un altro, l'esempio imposta 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! 
) 

Questo visualizza:

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

In questo caso, sembra che io debba abilitare l'espansione ritardata nel CALLER, il che potrebbe essere una seccatura.

67
Brown

Le variabili di ambiente nei file batch vengono espanse quando viene analizzata una riga. Nel caso di blocchi delimitati da parentesi (come if defined) l'intero blocco conta come una "riga" o comando.

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

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

setlocal enabledelayedexpansion

L'espansione ritardata causa la valutazione delle variabili delimitate da punti esclamativi (!) all'esecuzione anziché l'analisi, che assicureranno il comportamento corretto nel tuo caso:

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

help set dettagli anche questo:

Infine, è stato aggiunto il supporto per l'espansione della variabile di ambiente ritardata. Questo supporto è sempre disabilitato per impostazione predefinita, ma può essere abilitato/disabilitato tramite l'opzione della riga di comando /V su CMD.EXE. Vedi CMD /?

L'espansione della variabile di ambiente ritardata è utile per aggirare i limiti dell'espansione corrente che si verifica quando viene letta una riga di testo, non quando viene eseguita. Nell'esempio seguente viene illustrato il problema con l'espansione della variabile immediata:

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

non visualizzerebbe mai il messaggio, dal momento che %VAR% in entrambe le istruzioni IF vengono sostituite quando viene letta la prima istruzione IF, poiché include logicamente il corpo del IF , che è una dichiarazione composta. Quindi l'IF all'interno dell'istruzione composta sta davvero confrontando "prima" con "dopo" che non sarà mai uguale. Allo stesso modo, il seguente esempio non funzionerà come previsto:

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

nel senso che not costruisce un elenco di file nella directory corrente, ma invece imposterà la variabile LIST all'ultimo file trovato. Ancora, questo perché il %LIST% è espanso una volta sola quando viene letta l'istruzione FOR, e in quel momento la variabile LIST è vuota. Quindi il ciclo FOR attuale che stiamo eseguendo è:

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

che continua a impostare LIST all'ultimo file trovato.

L'espansione della variabile di ambiente ritardata consente di utilizzare un carattere diverso (il punto esclamativo) per espandere le variabili di ambiente al momento dell'esecuzione. Se l'espansione variabile ritardata è abilitata, gli esempi precedenti potrebbero essere scritti come segue per funzionare 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%
81
Joey

Lo stesso comportamento si verifica anche quando i comandi si trovano su una singola riga (& è il separatore dei comandi):

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

La spiegazione di Joey è la mia preferita. Si noti tuttavia che enabledelayedexpansion non funziona su Windows NT 4.0 (e non sono sicuro di Windows 2000).

Sulla tua domanda di follow-up, no, non è possibile EnableDelayedExpansion senza setlocal. Comunque il comportamento originale che stava andando contro di te può essere usato per lavorare su quel secondo problema: il trucco è quello di endlocal sulla stessa linea dove si impostano nuovamente i valori delle variabili che ti servono.

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 ecco un'altra soluzione a questo problema: utilizzare una procedura nello stesso file anziché in un blocco in linea o in un file esterno.

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

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

Se non funziona in questo modo, è probabile che sia stata ritardata l'espansione della variabile di ambiente. Puoi spegnerlo con cmd /V:OFF o usare punti esclamativi all'interno del tuo se:

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

Questo succede perché la tua linea con comando FOR valuta solo una volta. Hai bisogno di un modo per rivalutarlo. È possibile simulare un'espansione ritardata con il comando CALL:

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

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

per esempio.

   if not defined BAR set FOO=1

imposterà FOO su 1. Potresti quindi fare un altro controllo come:

   if not defined BAR echo FOO: %FOO%

Ehi, non ho mai detto che fosse carina. Ma se non vuoi fare confusione con il setlocal o il business di espansione ritardato, è una soluzione rapida.

0
demoncodemonkey

A volte, come nel mio caso, quando è necessario essere compatibili con sistemi 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 si utilizzi Delayd Expansion, si raccomanda di includere almeno il check-in batch:

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

Se vuoi solo un approccio più leggibile e semplice, ecco le 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 in questo modo:

>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 un'altra soluzione di cmd pura:

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 questa è la soluzione di sintassi IF THEN ELSE completa:

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
0
Arunas Bartisius