¿Las variables en el archivo por lotes no se configuran dentro de IF?

69

Tengo dos ejemplos de archivos por lotes muy simples:

Asignación de un valor a una variable:

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

Lo cual, como se esperaba, da como resultado:

FOO: 1 
Press any key to continue . . .

Sin embargo, si coloco las mismas dos líneas dentro de un bloque SI NO ES DEFINIDO:

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

Esto inesperadamente resulta en:

FOO: 
Press any key to continue . . .

Esto no debería tener nada que ver con el IF, claramente el bloque se está ejecutando. Si defino BAR encima del if, solo se muestra el texto del comando PAUSE, como se esperaba.

¿Lo que da?


Pregunta de seguimiento: ¿Hay alguna forma de habilitar la expansión retrasada sin setlocal?

Si tuviera que llamar a este simple archivo por lotes de ejemplo desde otro, el ejemplo establece FOO, pero solo LOCALMENTE.

Por ejemplo:

testcaller.bat

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

test.bat

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

Esto muestra:

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

En este caso, parece que tengo que habilitar la expansión retrasada en el LLAMADOR, lo que puede ser una molestia.

marrón
fuente

Respuestas:

84

Las variables de entorno en los archivos por lotes se expanden cuando se analiza una línea. En el caso de bloques delimitados por paréntesis (como el suyo if defined), el bloque completo cuenta como una "línea" o comando.

Esto significa que todas las apariciones de% FOO% se reemplazan por sus valores antes de ejecutar el bloque. En su caso sin nada, ya que la variable aún no tiene un valor.

Para resolver esto, puede habilitar la expansión demorada :

setlocal enabledelayedexpansion

La expansión retrasada hace que las variables delimitadas por signos de exclamación ( !) se evalúen en la ejecución en lugar de analizarlas, lo que garantizará el comportamiento correcto en su caso:

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

help set detalla esto también:

Finalmente, se ha agregado soporte para la expansión variable de entorno retrasada. Este soporte siempre está deshabilitado de manera predeterminada, pero puede habilitarse / deshabilitarse mediante el /Vinterruptor de línea de comando a CMD.EXE. VerCMD /?

La expansión variable de entorno retardada es útil para sortear las limitaciones de la expansión actual que ocurre cuando se lee una línea de texto, no cuando se ejecuta. El siguiente ejemplo demuestra el problema con la expansión variable inmediata:

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

nunca mostraría el mensaje, ya que el %VAR%en ambas IF declaraciones se sustituye cuando se lee la primera declaración IF, ya que lógicamente incluye el cuerpo de la IF, que es una declaración compuesta. Entonces, el IF dentro de la declaración compuesta realmente está comparando "antes" con "después" que nunca será igual. Del mismo modo, el siguiente ejemplo no funcionará como se esperaba:

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

en el que va a no crear una lista de archivos en el directorio actual, sino que se acaba de establecer la LIST variable para el último archivo encontrado. Nuevamente, esto se debe a que %LIST%se expande solo una vez cuando FOR se lee la declaración, y en ese momento la LISTvariable está vacía. Entonces, el bucle FOR real que estamos ejecutando es:

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

que solo sigue configurando LISTel último archivo encontrado.

La expansión retardada de la variable de entorno le permite usar un carácter diferente (el signo de exclamación) para expandir las variables de entorno en el momento de la ejecución. Si la expansión variable retardada está habilitada, los ejemplos anteriores podrían escribirse de la siguiente manera para que funcionen según lo 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%
Joey
fuente
17
Y si necesita hacer eco de un !, use ^^^!(escape dos veces). De lo contrario, la función de "expansión retrasada" se lo comerá.
Grawity
4

El mismo comportamiento también ocurre cuando los comandos están en una sola línea ( &es el separador de comandos):

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

La explicación de Joey es mi favorita. Sin embargo, enabledelayedexpansiontenga en cuenta que eso no funciona en Windows NT 4.0 (y no estoy seguro acerca de Windows 2000).

Acerca de su pregunta de seguimiento, no, no es posible EnableDelayedExpansionprescindir setlocal. Sin embargo, el comportamiento original que iba en contra de usted se puede utilizar para resolver ese segundo problema: el truco está endlocalen la misma línea donde establece nuevamente los valores de las variables que necesita.

Aquí está tu test.batmodificado:

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

Pero aquí hay otra solución a ese problema: use un procedimiento en el mismo archivo en lugar de un bloque en línea o un archivo externo.

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

:NotDefined
set FOO=1
echo FOO: %FOO%
goto :EOF
dolmen
fuente
"El truco es endlocal en la misma línea" - ¡GRACIAS por eso!
dforce
3

Si no funciona de esa manera, es probable que haya retrasado la expansión de la variable de entorno. Puede desactivarlo cmd /V:OFFo usar signos de exclamación dentro de su if:

@echo off
IF NOT DEFINED BAR (
    set FOO=1
    echo FOO: !FOO!
)
pause
echo on
John T
fuente
3
La activación de la expansión retardada solo altera la semántica del signo de exclamación. No tiene ningún efecto en la expansión variable normal. Además, habilitarlo accidentalmente es muy poco probable; las personas que lo tienen habilitado generalmente lo hacen a propósito.
Joey el
1

Esto sucede porque su línea con FORcomando se evalúa solo una vez. Necesitas alguna forma de reevaluarlo. Puede simular una expansión demorada con el CALLcomando:

for /l %%I in (0,1,5) do call echo %%RANDOM%%
Consultoría gratuita
fuente
La expansión retrasada no funcionó, pero esto / do call / funcionó en win7, para mi script cmd de 3 líneas para encontrar el enlace real: @for / f "delims =" %% a in ('bash ~ / perl / cwd2 .sh% * ') hacer conjunto de llamadas CWD_REAL = %% a echo cd / d% CWD_REAL% cd / d% CWD_REAL%
mosh
0

Vale la pena señalar que sí funciona si es un solo comando en la misma línea.

p.ej

   if not defined BAR set FOO=1

en realidad se establecerá FOOen 1. A continuación, puede hacer otra comprobación como:

   if not defined BAR echo FOO: %FOO%

Oye, nunca dije que fuera bonito. Pero si no desea meterse con setlocal o el negocio de expansión demorada, es una solución rápida.

demoncodemonkey
fuente
0

A veces, como lo hice en mi caso, cuando necesita ser compatible con sistemas heredados como los viejos servidores NT4 donde la expansión retrasada no funciona o por alguna razón no funciona, es posible que necesite una solución alternativa.

En caso de que use Expansión Delayd, también recomendó que se incluya al menos el check in en lote:

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

En caso de que solo desee un enfoque más legible y simple, aquí hay soluciones alternativas:

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

que funciona así:

>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

y una solución cmd pura más:

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

funciona así:

>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

y esto está lleno SI ENTONCES otra solución de sintaxis:

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%

funciona así:

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