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


69

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

Che, come previsto, si traduce in:

FOO: 1 
Press any key to continue . . .

Tuttavia, se inserisco le stesse due linee all'interno di un blocco SE NON DEFINITO:

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

Ciò si traduce inaspettatamente 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 l'if, viene visualizzato solo il testo del comando PAUSE, come previsto.

Cosa dà?


Domanda di follow-up: esiste un modo per abilitare l'espansione ritardata senza setlocal?

Se dovessi chiamare questo semplice esempio di file batch da 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 mostra:

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

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

Risposte:


84

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

Ciò significa che tutte le occorrenze di% FOO% vengono sostituite dai loro valori prima dell'esecuzione del blocco. Nel tuo caso senza nulla, poiché la variabile non ha ancora un valore.

Per risolvere questo problema puoi abilitare l'espansione ritardata :

setlocal enabledelayedexpansion

L'espansione ritardata causa la !valutazione delle variabili delimitate da punti esclamativi ( ) invece dell'esecuzione, che garantirà 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 ritardata delle variabili d'ambiente. Questo supporto è sempre disabilitato per impostazione predefinita, ma può essere abilitato / disabilitato tramite l' /Vinterruttore della riga di comando su CMD.EXE. VedereCMD /?

L'espansione ritardata della variabile d'ambiente è utile per aggirare i limiti dell'espansione corrente che si verifica quando viene letta una riga di testo, non quando viene eseguita. L'esempio seguente mostra 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 visualizzerebbe mai il messaggio, poiché %VAR%in entrambe le IF istruzioni viene sostituita quando viene letta la prima istruzione IF, poiché include logicamente il corpo di IF, che è un'istruzione 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%

in quanto sarà non costruire un elenco di file nella directory corrente, ma invece sarà solo impostare la LIST variabile per l'ultimo file trovato. Ancora una volta, ciò è dovuto al fatto che %LIST%viene espanso solo una volta quando FOR viene letta l' istruzione e in quel momento la LISTvariabile è vuota. Quindi l'effettivo ciclo FOR che stiamo eseguendo è:

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

che continua a impostare LISTl'ultimo file trovato.

L'espansione ritardata delle variabili di ambiente consente di utilizzare un carattere diverso (il punto esclamativo) per espandere le variabili di ambiente al momento dell'esecuzione. Se è abilitata l'espansione variabile ritardata, gli esempi sopra 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%

17
E se hai bisogno di fare eco a !, usa ^^^!(scappa due volte). Altrimenti la funzione "espansione ritardata" la consumerà.
Grawity

4

Lo stesso comportamento si verifica anche quando i comandi sono su una sola 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 enabledelayedexpansionnon funziona su Windows NT 4.0 (e non sono sicuro su Windows 2000).

Per quanto riguarda la tua domanda di follow-up, no, non è possibile farlo EnableDelayedExpansionsenza setlocal. Tuttavia, il comportamento originale che si stava opponendo a te può essere utilizzato per aggirare il secondo problema: il trucco è quello endlocaldi impostare nuovamente i valori delle variabili necessarie sulla stessa riga.

Ecco la tua test.batmodifica:

@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é un blocco inline o un file esterno.

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

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

"il trucco è endlocale sulla stessa linea" - GRAZIE per quello!
Forza

3

Se non funziona in questo modo, probabilmente hai ritardato l'espansione della variabile d'ambiente. Puoi disattivarlo con cmd /V:OFFo utilizzare i punti esclamativi all'interno di se:

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

3
L'abilitazione dell'espansione ritardata modifica solo la semantica del punto esclamativo. Non ha alcun effetto sulla normale espansione variabile. Inoltre, abilitarlo accidentalmente è molto improbabile; le persone che lo hanno abilitato di solito lo fanno apposta.
Joey,

1

Ciò accade perché la riga con il FORcomando viene valutata una sola volta. Hai bisogno di un modo per rivalutarlo. È possibile simulare un'espansione ritardata con il CALLcomando:

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

L'espansione ritardata non ha funzionato, ma questo / do call / ha funzionato su win7, per il mio script cmd a 3 righe per trovare il vero hardlink: @for / f "delims =" %% a in ('bash ~ / perl / cwd2 .sh% * ') chiama il set CWD_REAL = %% a echo cd / d% CWD_REAL% cd / d% CWD_REAL%
mosh

0

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

per esempio

   if not defined BAR set FOO=1

sarà effettivamente impostato FOOsu 1. È quindi possibile effettuare un altro controllo come:

   if not defined BAR echo FOO: %FOO%

Ehi, non ho mai detto che fosse carino. Ma se non vuoi fare confusione con setlocal o l'attività di espansione ritardata, è una soluzione rapida.


0

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 utilizzerai Delayd Expansion, ti consigliamo anche di includere almeno il check-in batch:

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

Nel caso in cui desideri solo un approccio più 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 un'altra soluzione 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 questo è completo SE POI SULLA soluzione di sintassi 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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.