El número aleatorio en un archivo por lotes no cambia

2

¿Cómo hacer que este script funcione bajo el símbolo del sistema ?, ahora todos los números aleatorios son iguales

SET SOME[1]="AA"
SET SOME[2]="BB"
SET SOME[3]="CC"
SET SOME[4]="DD"
SET SOME[5]="EE"
FOR /L %%i IN (1,1,5) DO FOR /F %%j IN ('SET RND=%%RANDOM%%*5/32768+1') DO ECHO SOME[%%i] %%j
nerdwaller
fuente
3
Obligatorio XKCD
Rich Homolka

Respuestas:

7

Wow, tienes muchos problemas con ese pequeño fragmento de código :-)

A su código publicado le falta la opción SET / A. Supongo que su código real lo tiene.

La razón por la que su código falla con un error de sintaxis es porque el comando dentro de la cláusula FOR IN () se ejecuta a través de un cmd /C yourCommandHerecomando. Cuando cmd /Cse analiza el comando implícito , se trata =como un delimitador de token a menos que se escape o se cite. Cualquier cadena consecutiva de delimitadores de token se convierte en una sola <space>antes de que su comando se ejecute realmente en un nuevo hilo CMD utilizando la semántica de línea de comando. La lista de delimitadores de tokens es , ; = <space> <non-breaking space>y <tab>.

Citar el comando eliminará el error de sintaxis:

FOR /L %%i IN (1,1,5) DO FOR /F %%j IN ('SET /A "RND=%%RANDOM%%*5/32768+1"') DO ECHO SOME[%%i] %%j

Como se escapará el =:

FOR /L %%i IN (1,1,5) DO FOR /F %%j IN ('SET /A RND^=%%RANDOM%%*5/32768+1') DO ECHO SOME[%%i] %%j

Pero realmente no necesita asignar el número aleatorio a una variable. El comando FOR IN () se ejecuta dentro de un contexto de línea de comando, y SET / A imprimirá el valor calculado en stdout cuando se ejecute dentro de un contexto de línea de comando. Entonces, lo siguiente también elimina cualquier error de sintaxis con efectivamente los mismos resultados:

FOR /L %%i IN (1,1,5) DO FOR /F %%j IN ('SET /A %%RANDOM%%*5/32768+1') DO ECHO SOME[%%i] %%j

Aquí hay un método más simple para dar un resultado del 1 al 5 (mod aleatorio 5 + 1):

FOR /L %%i IN (1,1,5) DO FOR /F %%j IN ('SET /A %%RANDOM%% %% 5 + 1') DO ECHO SOME[%%i] %%j

Pero dudo seriamente que cualquiera de las soluciones anteriores dé el resultado deseado.

Algo muy peculiar sucede con el valor de %RANDOM%. Su uso de %%RANDOM%%correctamente hace que la expresión se evalúe en cada iteración. Pero por alguna razón, el número aleatorio es casi constante para cualquier ejecución dada. De vez en cuando, una de las iteraciones variará, pero para la mayoría de las ejecuciones, cada iteración obtiene un valor constante. Creo que debe tener algo que ver con el valor inicial del generador de números aleatorios. Quizás el generador de números aleatorios se inicia con un valor inicial cada vez que se inicia una sesión CMD, y el valor inicial solo cambia muy lentamente. Recuerde que la cláusula FOR IN () se ejecuta en una nueva sesión CMD.

Aquí hay un programa de prueba que demuestra que %%test%%se está reevaluando adecuadamente cada iteración. También muestra cómo %%random%%se mantiene casi constante dentro de una carrera.

@echo off
setlocal
set test=0
for /l %%I in (1 1 5) do for /f "delims=" %%J in ('echo %%test%% %%random%%') do (
  echo %%J
  set "test=%%I
)

Aquí está la salida de 2 ejecuciones del código anterior. Observe cómo la primera ejecución tiene una variación en el número aleatorio. La segunda carrera tiene un valor aleatorio constante.

C:\test> test
0 20369
1 20373
2 20373
3 20373
4 20373

C:\test> test
0 20379
1 20379
2 20379
3 20379
4 20379

Realmente no hay razón para poner el cálculo de números aleatorios dentro de una cláusula FOR / F IN ('comando'). Todo es mucho más simple si usa SET / A con expansión retrasada directamente dentro de su bucle externo.

Creo que lo siguiente puede ser lo que estás buscando:

@echo off
setlocal enableDelayedExpansion
SET SOME[1]="AA"
SET SOME[2]="BB"
SET SOME[3]="CC"
SET SOME[4]="DD"
SET SOME[5]="EE"
FOR /L %%i IN (1,1,5) DO (
  set /a rand=!random!%%5+1
  for %%N in (!rand!) do echo %%i: SOME[%%N]=!SOME[%%N]!
)

Aquí hay algunos resultados de muestra:

C:\test>test
1: SOME[3]="CC"
2: SOME[5]="EE"
3: SOME[2]="BB"
4: SOME[2]="BB"
5: SOME[5]="EE"

EDITAR

Aquí hay una mejor evidencia de que el aleatorizador para la sesión CMD se reinicia y la semilla cambia lentamente.

@echo off
setlocal enableDelayedExpansion

echo Within a single CMD session, every ^^!random^^! gets a new value.
for /l %%N in (1 1 10) do call echo !time! !random! !random!

echo(

setlocal disableDelayedExpansion
echo But each CMD session !random! is reseeded,
echo and the seed only changes once per second,
echo and the inital value changes slowly:
for /l %%N in (1 1 30) do cmd /v:on /c "echo !time! !random! !random!&for /l %%A in (1 1 50000) do @rem"

--SALIDA--

Within a single CMD session, every !random! gets a new value.
11:12:10.37 17810 1733
11:12:10.37 8919 24464
11:12:10.37 9931 2137
11:12:10.37 28574 16630
11:12:10.37 30379 23234
11:12:10.37 22410 31740
11:12:10.38 15479 14080
11:12:10.38 812 23616
11:12:10.38 1384 25909
11:12:10.38 2733 1947

But each CMD session !random! is reseeded,
and the seed only changes once per second,
and the inital value changes slowly:
11:12:10.39 4552 6316
11:12:10.50 4552 6316
11:12:10.61 4552 6316
11:12:10.71 4552 6316
11:12:10.82 4552 6316
11:12:10.92 4552 6316
11:12:11.03 4555 17064
11:12:11.14 4555 17064
11:12:11.24 4555 17064
11:12:11.35 4555 17064
11:12:11.45 4555 17064
11:12:11.56 4555 17064
11:12:11.67 4555 17064
11:12:11.77 4555 17064
11:12:11.88 4555 17064
11:12:11.99 4555 17064
11:12:12.09 4559 27813
11:12:12.20 4559 27813
11:12:12.30 4559 27813
11:12:12.41 4559 27813
11:12:12.51 4559 27813
11:12:12.62 4559 27813
11:12:12.73 4559 27813
11:12:12.83 4559 27813
11:12:12.94 4559 27813
11:12:13.04 4562 5793
11:12:13.15 4562 5793
11:12:13.26 4562 5793
11:12:13.36 4562 5793
11:12:13.47 4562 5793
dbenham
fuente
Entonces, la expansión retrasada no es la solución, pero la usó en su solución. Entendido.
Patrick Seymour
@PatrickS. - La falta de expansión retrasada en el código original del OP no fue la fuente de los problemas, como intenté explicar en el medio de mi respuesta. En teoría, debería haber funcionado sin una expansión retrasada, pero cierta rareza en la generación de números aleatorios da como resultado un valor casi constante, a pesar de que se reevalúa en cada iteración. Mi penúltimo código demuestra que %%RANDOM%%se está reevaluando correctamente. Con el fin de conseguir alrededor de los problemas, Reestructuré el código de una manera que no requiere expansión retardada.
dbenham
@PatrickS: el código original era solo parte de scripts complejos mucho más grandes, cuya construcción tiene la intención de aclarar algunas preguntas. Que en realidad consisten en muchos problemas en total. 1. La expansión retrasada de% random%. 2. El comportamiento anidado de cmd / c en un bucle for 3. La división entera de la variable aleatoria, que en realidad causa problemas más adelante 4. La naturaleza anidada de% random% se resuelve en diferentes niveles de anidamiento. Es por eso que la solución dbenham está más completa y explicada.
3

Dado que está expandiendo la %RANDOM%variable en un FORbucle, debe usar la expansión retardada.

Esto produce 5 ecos del mismo número:

FOR /L %%i IN (1,1,5) DO (
echo %random%
)

Esto produce 5 números "aleatorios":

setlocal ENABLEDELAYEDEXPANSION

FOR /L %%i IN (1,1,5) DO (
echo !random!
)

Puede encontrar más información sobre la expansión retrasada ejecutando

SET /?
Patrick Seymour
fuente
solo necesito eso del 1 al 5 ... es por eso que necesito una instrucción SET
2
Puede hacerlo con el SET que ya tenía. Estaba demostrando una expansión demorada. Su comando set sería algo así como: SET / A RND =! RANDOM! * 5/32768 + 1
Patrick Seymour el
1
Supongo que probé mi solución, pero realmente necesito alguna explicación de por qué el signo = debe escaparse con ^ ?? No puedo decir que los documentos encontrados en cualquier lugar ...
La expansión retrasada es a menudo la solución a muchos problemas. Pero no es así en este caso. El (los) problema (s) con el código del OP es en realidad endiabladamente complicado y sutil. Ver mi respuesta
dbenham
2

En este caso lo necesitas delayed expansion.

echo off & setlocal enabledelayedexpansion
set test=0
for /l %%I in (1 1 5) do for /f "delims=" %%J in ('echo !test! !random!') do (
  echo %%J
  set "test=%%I"
)

Esto es más rápido que %%test%%. Ejemplo de salida:

0 26542
1 32475
2 25609
3 4495
4 6719

Esto también funciona (más lento):

echo off & setlocal enabledelayedexpansion
set test=0
for /l %%I in (1 1 5) do for /f "delims=" %%J in ('cmd /v:on /c echo !test! !random!') do (
  echo %%J
  set "test=%%I"
)

Pero lo siguiente no funciona:

echo off & setlocal
set test=0
for /l %%I in (1 1 5) do for /f "delims=" %%J in ('cmd /v:on /c echo !test! !random!') do (
  echo %%J
  set "test=%%I"
)

.

0 19919
1 19919
2 19919
3 19919
4 19919
Endoro
fuente
1
La expansión retrasada en su segundo a último código está ocurriendo dentro del archivo por lotes principal, y funciona bien allí. La expansión retrasada en su último código está ocurriendo en la nueva sesión CMD implícita, donde no puede proporcionar un número aleatorio correctamente. Pero no es realmente constante; ocasionalmente da múltiples valores dentro de una carrera, como se evidencia en mi respuesta. Este es un comportamiento totalmente inesperado y fascinante.
dbenham