2008-11-20 12 views
17

Estoy escribiendo un archivo .bat simple y me he encontrado con un comportamiento extraño. Hay un par de lugares donde tengo que hacer un if/else simple, pero el código dentro de los bloques no parece estar funcionando correctamente.Problema de alcance extraño en el archivo .bat

Aquí es un caso sencillo que muestra el error:

@echo off 

set MODE=FOOBAR 

if "%~1"=="" (
    set MODE=all 
    echo mode: %MODE% 
) else (
    set MODE=%~1 
    echo mode: %MODE% 
) 
echo mode: %MODE% 

La salida que estoy recibiendo es:

C:\>test.bat test 
mode: FOOBAR 
mode: test 

¿Por qué es el eco en el interior del bloque de código no conseguir el nuevo valor de la ¿variable? En el código real que estoy escribiendo necesito construir algunas variables y hacer referencia a ellas dentro del alcance de if/else. Podría cambiar esto para usar etiquetas y gotos en lugar de if/else, pero eso no parece tan limpio.

¿Cuál es la causa de este comportamiento? ¿Hay algún tipo de límite en las variables dentro de los bloques de código?

Respuesta

26

Se encuentra con el problema de la expansión de la variable estática de cmd. La variable MODE solo se evalúa una vez. Puedes ver esto si omites el @echo fuera de línea.

Desde el conjunto /? documentación:

Finally, support for delayed environment variable expansion has been added. This support is always disabled by default, but may be enabled/disabled via the /V command line switch to CMD.EXE. See CMD /?

Delayed environment variable expansion is useful for getting around the limitations of the current expansion which happens when a line of text is read, not when it is executed. The following example demonstrates the problem with immediate variable expansion:

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

would never display the message, since the %VAR% in BOTH IF statements is substituted when the first IF statement is read, since it logically includes the body of the IF, which is a compound statement. So the IF inside the compound statement is really comparing "before" with "after" which will never be equal. Similarly, the following example will not work as expected:

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

in that it will NOT build up a list of files in the current directory, but instead will just set the LIST variable to the last file found. Again, this is because the %LIST% is expanded just once when the FOR statement is read, and at that time the LIST variable is empty. So the actual FOR loop we are executing is:

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

which just keeps setting LIST to the last file found.

Delayed environment variable expansion allows you to use a different character (the exclamation mark) to expand environment variables at execution time. If delayed variable expansion is enabled, the above examples could be written as follows to work as intended:

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% 
+0

¿Hay una manera de establecer esa bandera programáticamente en la parte superior de un archivo bat? La mayoría de las veces esto se ejecutará con otro archivo bat, y el entorno en el que se ejecuta no es fijo (se puede hacer doble clic, se puede ejecutar desde un shell cygwin, puede ser de cmd, etc.). – Herms

+3

@Herms, "setlocal enabledelayedexpansion" al inicio, "endlocal" al final. – paxdiablo

+0

Gracias. Esto salvó mi trasero paxdiablo. –

-2

Parece que las reglas de lectura y escritura utilizan diferentes ámbitos.

Si se elimina esta línea

set MODE=FOOBAR 

que va a funcionar como se espera. Por lo tanto, es probable que necesite tener una serie compleja si es necesario para obtener las variables que desee.

+0

En realidad no, no es así. Ese conjunto en la parte superior no estaba originalmente allí. Si omito eso, la primera vez que ejecuto el archivo bat, el primer eco está vacío. La segunda vez que lo ejecuto (desde la misma instancia de cmd), el primer eco muestra el último valor utilizado. – Herms

+0

Tienes razón, correría el guión por segunda vez ... ¡bien visto! –

+0

Eliminar su respuesta? –

2

EnableDelayedExpansion setlocal

permitirá al/v bandera

Cuestiones relacionadas