Problemas con SET y FOR

01/03/2007 - 10:38 por vjsanchez | Informe spam
¿Alguien sabe porqué no obtengo el mismo resultado en estos dos trozos de
código en un archivo de proceso por lotes (.bat)?:

echo *** ITERATIVE ***
echo.

set /a ERROR=0
for %%n in (1 2 3) do (
call non_existent_command
set /a ERROR ^|= %ERRORLEVEL%
echo ERROR is %ERROR% and ERRORLEVEL is %ERRORLEVEL%)

echo *** SEQUENTIAL ***
echo.

set /a ERROR=0
call non_existent_command
set /a ERROR ^|= %ERRORLEVEL%
echo ERROR is %ERROR% and ERRORLEVEL is %ERRORLEVEL%
call non_existent_command
set /a ERROR ^|= %ERRORLEVEL%
echo ERROR is %ERROR% and ERRORLEVEL is %ERRORLEVEL%
call non_existent_command
set /a ERROR ^|= %ERRORLEVEL%
echo ERROR is %ERROR% and ERRORLEVEL is %ERRORLEVEL%
 

Leer las respuestas

#1 Ramón Sola [MVP Windows - Shell/User]
02/03/2007 - 04:31 | Informe spam
La diferencia de comportamiento tiene relación con el momento en que se expanden
las variables de entorno. Al principio será complicado asimilar el funcionamiento
de la expansión y el modo en que se puede retardar hasta que se necesite
realmente, por eso este mensaje es tan largo.

En el código iterativo, las variables del cuerpo del FOR se sustituyen por sus
valores en el momento en que el FOR se va a ejecutar por primera vez.

Es decir, esto:

set /a ERROR=0
for %%n in (1 2 3) do (
call non_existent_command
set /a ERROR ^|= %ERRORLEVEL%
echo ERROR is %ERROR% and ERRORLEVEL is %ERRORLEVEL%)

Se transforma en estas sentencias durante la ejecución, si se supone ERRORLEVEL=0
al inicio:

set /a ERROR=0
for %%n in (1 2 3) do (
call non_existent_command
set /a ERROR ^|= 0
echo ERROR is 0 and ERRORLEVEL is 0)

Si el ERRORLEVEL inicial es 5, por ejemplo, el resultado sería este:

set /a ERROR=0
for %%n in (1 2 3) do (
call non_existent_command
set /a ERROR ^|= 5
echo ERROR is 0 and ERRORLEVEL is 5)

¿Cómo evitar esto? Tiene que haber una manera de retardar la expansión de las
variables hasta el mismo momento de ejecutar las sentencias básicas que las
contienen. En lugar de los signos de porcentaje habituales, se utilizarán signos
de exclamación. Cuidado, esta expansión retardada viene deshabilitada por
defecto. Hay tres formas de habilitarla:
* Valor DelayedExpansion de tipo DWORD igual a 1 en una de estas claves del
registro:
Configuración de usuario
HKEY_CURRENT_USER\SOFTWARE\Microsoft\Command Processor
Configuración de máquina
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Command Processor

* Intérprete de comandos invocado con el parámetro "/V:ON": CMD /V:ON
(CMD /V:OFF para deshabilitar la expansión en ese intérprete.)

* Sentencia SETLOCAL ENABLEDELAYEDEXPANSION en un archivo por lotes. Además, se
habilita un entorno local de forma que, a partir de dicha sentencia, los cambios
sobre las variables de entorno sólo tendrán efecto hasta la próxima sentencia
ENDLOCAL o hasta que termine la ejecución del archivo por lotes.

Si la expansión retardada no está habilitada, los signos de admiración y el
nombre de la variable se interpretan como una cadena literal. Ejemplo:
C:\WINDOWS>echo !CD! %CD%
!CD! C:\WINDOWS

(CD es una variable predefinida cuyo valor es el directorio actual.)

Igual para una variable que no esté definida:
C:\>echo !PERICODELOSPALOTES! %PERICODELOSPALOTES%
!PERICODELOSPALOTES! %PERICODELOSPALOTES%


El código iterativo modificado con la expansión retardada quedaría así:

set /a ERROR=0
for %%n in (1 2 3) do (
call non_existent_command
set /a ERROR ^|= !ERRORLEVEL!
echo ERROR is !ERROR! and ERRORLEVEL is !ERRORLEVEL!)


Además, este código expone una posible inconsistencia de la orden SET. Parece ser
que no modifica el estado de ERRORLEVEL al ejecutarla desde la línea de comandos.
Sin embargo, cuando se ejecuta SET correctamente en un archivo por lotes, se
reestablece ERRORLEVEL a 0. Esa es la razón por la que, al ejecutar el fragmento
anterior de código desde un archivo por lotes, aparece finalmente "and ERRORLEVEL
is 0". Si insertamos la sentencia ECHO !ERRORLEVEL! antes y después del SET
podremos observar este fenómeno.


Puedes observar cómo se comporta la expansión retardada frente a la expansión
"normal" ejecutando estas líneas de forma interactiva (o en un archivo por lotes
reemplazando %A por %%A). Para este propósito se utilizará RANDOM, una variable
predefinida de Cmd.exe que se sustituye cada vez por un valor aleatorio entre 0 y
32767.

for %A in (1 2 3) do @echo %RANDOM% %RANDOM%
for %A in (1 2 3) do @echo !RANDOM! %RANDOM%
for %A in (1 2 3) do @echo !RANDOM! !RANDOM!


Primera línea. Antes de ejecutar la sentencia FOR, las dos apariciones de la
variable RANDOM se sustituyen por sendos valores aleatorios. Por tanto aparece el
mismo par de valores tres veces.

Segunda línea. Antes de ejecutar la sentencia FOR, la segunda aparición de la
variable RANDOM se sustituye por un valor aleatorio. Debido a la expansión
retardada, la primera aparición de RANDOM se sustituye por un valor aleatorio
diferente en cada ejecución del cuerpo del FOR. Por tanto el primer número será
el mismo, pero el segundo variará.

Tercera línea. Debido a la expansión retardada, ambas apariciones de RANDOM se
sustituyen por números aleatorios diferentes en cada ejecución del cuerpo del
FOR. Por tanto se verán tres pares diferentes de números.


El código secuencial no presenta inconvenientes porque no hay sentencias que
dependan de otras, como el cuerpo de un FOR y el FOR propiamente dicho, las
partes de una sentencia IF o IF-ELSE, o sentencias encadenadas mediante los
símbolos "&", "&&" o "||".


Ramón Sola / / MVP Windows - Shell/User
Para obtener la dirección correcta no hacen falta los sellos.
Por favor, usar el correo sólo para cuestiones ajenas a los
grupos de noticias, gracias.

Un buen día, vjsanchez () tuvo la
irrefrenable necesidad de escribir:
¿Alguien sabe porqué no obtengo el mismo resultado en estos dos trozos de
código en un archivo de proceso por lotes (.bat)?:

echo *** ITERATIVE ***
echo.

set /a ERROR=0
for %%n in (1 2 3) do (
call non_existent_command
set /a ERROR ^|= %ERRORLEVEL%
echo ERROR is %ERROR% and ERRORLEVEL is %ERRORLEVEL%)

echo *** SEQUENTIAL ***
echo.

set /a ERROR=0
call non_existent_command
set /a ERROR ^|= %ERRORLEVEL%
echo ERROR is %ERROR% and ERRORLEVEL is %ERRORLEVEL%
call non_existent_command
set /a ERROR ^|= %ERRORLEVEL%
echo ERROR is %ERROR% and ERRORLEVEL is %ERRORLEVEL%
call non_existent_command
set /a ERROR ^|= %ERRORLEVEL%
echo ERROR is %ERROR% and ERRORLEVEL is %ERRORLEVEL%

Preguntas similares