Generador aleatorio en el lote

Tengo un archivo bat

@echo %RANDOM% 

y ejecutarlo usando la línea de comando

 start randomcheck.bat & start randomcheck.bat 

Se abren dos consolas y ambas contienen el mismo número, 4645. Esto no tiene el propósito de proporcionar al azar diferentes carpetas temporales (solo necesito carpetas aleatorias cuando las utilizo simulativamente). ¿Cómo tiene generador aleatorio normal en lote?

La actualización https://stackoverflow.com/a/19697361/1083704 ha cuantificado el período global de actualización de semillas. La semilla global de shell de Windows se actualiza cada segundo. Prácticamente, debo agregar un segundo más por margen de seguridad, para evitar las carreras y espero que esto sea medida suficiente. Esto realmente apesta. Significa que lanzar 8 procesos para mi iCore7 tomará 16 segundos. Y todavía no estoy seguro de que esto tenga éxito, ya que no se ha especificado oficialmente nada y aún es posible que a pesar de que 8 procesos se inicien inicialmente con un desplazamiento temporal relativo, puede suceder que dos procesos finalicen al mismo tiempo y debo cuidar de que sean no reiniciado simultáneamente de nuevo. Esto es una gilipollez completa y mi pregunta era ¿se puede curar esto en el lote, sin recurrir a C ++ o VBScript?

MC ND es 100% correcto en todos los conteos dentro de su respuesta, y también su comentario de seguimiento.

Cada instancia de CMD.EXE inicializa el generador de números aleatorios al iniciar utilizando una semilla que se deriva de la hora actual con una resolución de 1 segundo. Todos los procesos CMD.EXE que se inicien dentro del mismo segundo obtendrán secuencias de números aleatorios idénticos.

Un aspecto adicional: el número aleatorio inicial para segundos consecutivos cambia muy lentamente. Me parece que el número aleatorio inicial puede ser realmente el valor inicial derivado del tiempo, pero no estoy seguro.

EDITAR – Originalmente había deducido todo esto a través de la experimentación. Pero desde entonces he visto la confirmación de una fuente autorizada .

Aquí hay una secuencia de comandos que demuestra cómo la semilla para CMD.EXE cambia solo una vez por segundo, y la semilla cambia muy lentamente:

 @echo off setlocal set "last=%time:~9,1%" for /l %%N in (1 1 30) do ( call :wait cmd /c echo %%time%% %%random%% %%random%% %%random%% %%random%% %%random%% %%random%% ) exit /b :wait if %time:~9,1% equ %last% goto :wait set "last=%time:~9,1%" exit /b 

– SALIDA 1 –

 22:13:26.31 30024 16831 1561 8633 8959 14378 22:13:26.41 30024 16831 1561 8633 8959 14378 22:13:26.51 30024 16831 1561 8633 8959 14378 22:13:26.61 30024 16831 1561 8633 8959 14378 22:13:26.71 30024 16831 1561 8633 8959 14378 22:13:26.81 30024 16831 1561 8633 8959 14378 22:13:26.91 30024 16831 1561 8633 8959 14378 22:13:27.01 30027 27580 19425 32697 19274 18304 22:13:27.11 30027 27580 19425 32697 19274 18304 22:13:27.21 30027 27580 19425 32697 19274 18304 22:13:27.31 30027 27580 19425 32697 19274 18304 22:13:27.41 30027 27580 19425 32697 19274 18304 22:13:27.51 30027 27580 19425 32697 19274 18304 22:13:27.61 30027 27580 19425 32697 19274 18304 22:13:27.71 30027 27580 19425 32697 19274 18304 22:13:27.81 30027 27580 19425 32697 19274 18304 22:13:27.91 30027 27580 19425 32697 19274 18304 22:13:28.01 30030 5560 4521 23992 29588 22231 22:13:28.11 30030 5560 4521 23992 29588 22231 22:13:28.21 30030 5560 4521 23992 29588 22231 22:13:28.31 30030 5560 4521 23992 29588 22231 22:13:28.41 30030 5560 4521 23992 29588 22231 22:13:28.51 30030 5560 4521 23992 29588 22231 22:13:28.61 30030 5560 4521 23992 29588 22231 22:13:28.71 30030 5560 4521 23992 29588 22231 22:13:28.81 30030 5560 4521 23992 29588 22231 22:13:28.91 30030 5560 4521 23992 29588 22231 22:13:29.01 30033 16308 22385 15287 7135 26158 22:13:29.11 30033 16308 22385 15287 7135 26158 22:13:29.21 30033 16308 22385 15287 7135 26158 

Este script demuestra que el generador de números aleatorios funciona “correctamente” dentro de un único proceso CMD.EXE.

 @echo off setlocal enableDelayedExpansion set "last=%time:~9,1%" for /l %%N in (1 1 30) do ( call :wait echo !time! !random! !random! !random! !random! !random! !random! ) exit /b :wait if %time:~9,1% equ %last% goto :wait set "last=%time:~9,1%" exit /b 

– SALIDA 2 –

 22:16:10.30 24175 26795 4467 2450 12031 9676 22:16:10.40 6873 17221 14201 17898 32541 29918 22:16:10.50 700 21044 25922 8616 24057 7657 22:16:10.60 25370 6519 26054 28443 4865 1931 22:16:10.70 26989 9396 12747 26808 6282 32182 22:16:10.80 22778 11460 11989 26055 10548 1809 22:16:10.90 4668 27372 30965 12923 5941 16533 22:16:11.00 23426 11396 24402 29658 5150 11183 22:16:11.10 1557 13572 18815 21801 4103 23119 22:16:11.20 3459 30126 20484 32750 3360 16811 22:16:11.30 14041 26960 31897 24736 16657 1954 22:16:11.40 5112 18377 30475 18837 12216 10237 22:16:11.50 13136 6241 27074 29398 8996 9738 22:16:11.60 16027 15122 13659 28897 4827 29753 22:16:11.70 27502 8271 11489 21888 16590 7886 22:16:11.80 30405 25582 7288 5432 7310 26557 22:16:11.90 202 11076 23205 20739 28053 12621 22:16:12.00 4234 20370 10355 5974 27590 8732 22:16:12.10 24411 21836 16161 24731 22898 10378 22:16:12.20 23060 17903 10788 19107 29825 15561 22:16:12.30 6772 1371 674 13257 15504 18422 22:16:12.40 1344 31971 23977 8630 10789 15367 22:16:12.50 18945 17823 20691 10497 5958 31613 22:16:12.60 18294 10398 26910 8744 21528 272 22:16:12.70 25603 9991 24084 11667 16977 5843 22:16:12.80 19405 5457 16285 11165 26783 10627 22:16:12.90 20041 31763 26390 11994 19285 12287 22:16:13.00 21342 13853 9336 24080 2555 2067 22:16:13.10 9328 30429 1722 2211 22934 24871 22:16:13.20 8168 21818 19125 11102 449 8813 

Finalmente, este script demuestra cómo cada %random% dentro de una línea determinada se expande a su propio valor, pero los valores de línea no cambian entre iteraciones de bucle porque la línea en bucle solo se analiza una vez.

 @echo off setlocal set "last=%time:~9,1%" for /l %%N in (1 1 30) do ( call :wait echo %time% %random% %random% %random% %random% %random% %random% ) exit /b :wait if %time:~9,1% equ %last% goto :wait set "last=%time:~9,1%" exit /b 

– SALIDA 3 –

 22:20:10.98 28188 30311 32299 7392 5874 32157 22:20:10.98 28188 30311 32299 7392 5874 32157 22:20:10.98 28188 30311 32299 7392 5874 32157 22:20:10.98 28188 30311 32299 7392 5874 32157 22:20:10.98 28188 30311 32299 7392 5874 32157 22:20:10.98 28188 30311 32299 7392 5874 32157 22:20:10.98 28188 30311 32299 7392 5874 32157 22:20:10.98 28188 30311 32299 7392 5874 32157 22:20:10.98 28188 30311 32299 7392 5874 32157 22:20:10.98 28188 30311 32299 7392 5874 32157 22:20:10.98 28188 30311 32299 7392 5874 32157 22:20:10.98 28188 30311 32299 7392 5874 32157 22:20:10.98 28188 30311 32299 7392 5874 32157 22:20:10.98 28188 30311 32299 7392 5874 32157 22:20:10.98 28188 30311 32299 7392 5874 32157 22:20:10.98 28188 30311 32299 7392 5874 32157 22:20:10.98 28188 30311 32299 7392 5874 32157 22:20:10.98 28188 30311 32299 7392 5874 32157 22:20:10.98 28188 30311 32299 7392 5874 32157 22:20:10.98 28188 30311 32299 7392 5874 32157 22:20:10.98 28188 30311 32299 7392 5874 32157 22:20:10.98 28188 30311 32299 7392 5874 32157 22:20:10.98 28188 30311 32299 7392 5874 32157 22:20:10.98 28188 30311 32299 7392 5874 32157 22:20:10.98 28188 30311 32299 7392 5874 32157 22:20:10.98 28188 30311 32299 7392 5874 32157 22:20:10.98 28188 30311 32299 7392 5874 32157 22:20:10.98 28188 30311 32299 7392 5874 32157 22:20:10.98 28188 30311 32299 7392 5874 32157 22:20:10.98 28188 30311 32299 7392 5874 32157 

El generador de números aleatorios en cmd usa la hora actual (segunda resolución) para sembrar el prng. Entonces, dos procesos que comienzan en el mismo segundo generarán el mismo número “aleatorio”.

Para una opción que no colisionará, use el generador de números aleatorios de vbscript (aleatorice primero), o use un guid (puede generarlo a través de uuidgen o también con vbscript), o powershell, o ….

  @set @e=0 /* @echo off set @e= cscript //nologo //e:jscript "%~f0" exit /b */ function getRandomNumber(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; } WScript.echo(getRandomNumber(0, 10000)); 

Incluso un generador de números aleatorios perfecto puede (eventualmente) generar colisiones. Una solución robusta debe asumir que las colisiones son posibles y compensar en consecuencia.

Aquí hay una estrategia que he utilizado con éxito en el pasado:

Asigna el nombre único del archivo temporal en la parte superior de tu script. Use el valor %TIME% para un número pseudo “aleatorio”. Reemplace el : con nada para que la cadena sea válida para un nombre de archivo. Dos procesos solo colisionarán si comienzan dentro de 1/100 segundos el uno del otro (suponiendo que sus procesos no se ejecutan durante más de un día).

Es posible que tengas colisiones. Las colisiones se pueden detectar a través de un archivo de locking temporal. Coloque el cuerpo principal de su script en una subrutina y llame a la rutina principal con un manejador de archivo no estándar redirigido a un archivo de locking con el número “aleatorio”; solo un proceso puede redirigir la salida al archivo de locking en cualquier momento dado. Si se detecta un locking, simplemente retroceda y vuelva a intentarlo.

 @echo off setlocal :getUnique :: Derive a pseudo "unique" name from script name and current time set "tempBase=%temp%\%~nx0.%time::=%" :: If you want to test the lock mechanism, uncomment the following :: line which removes the time component from the "unique" name ::set "tempBase=%temp%\%~nx0.notUnique" :: Save stderr, then redirect stderr to null 3>&2 2>nul ( %= Establish lock =% 9>"%tempBase%.lock" ( %= Restore stderr and call main routine =% 2>&3 (call :main %*) %= Capture the returned errorlevel if necessary =% call set "err=%%errorlevel%% %= Force ERRORLEVEL to 0 so that any error detected outside =% %= this block must be due to lock failure =% (call ) %= Loop back and try again if lock failed due to collision =% ) || goto :getUnique ) :: Delete the temp files and exit with the saved errorlevel del "%tempBase%*" exit /b %err% :main :: The rest of the script goes here. :: Additional unique temp file names can be derived from %tempBase% as needed. :: For this demo, I'll just list the temp file(s) and pause dir /b "%tempBase%*" pause :: Exit with an error for testing purposes exit /b 1 

Es poco probable que dos procesos obtengan el mismo nombre único, pero si lo hacen, el segundo proceso detectará la colisión, repetirá y volverá a intentar hasta que tenga éxito.

Quite la marca de la línea no única TempBase si desea probar el locking. Abra dos ventanas de consola e inicie el script en ambas ventanas. El primero ingresará con éxito a la rutina principal y hará una pausa. El segundo se repetirá, esperando que el primero termine. Presione una tecla en la primera, y la primera terminará instantáneamente y la segunda continuará en la rutina principal.

Si desea una precisión superior a 1/100 segundos, o si sus procesos pueden durar más de un día, considere usar WMIC OS GET LOCALDATETIME para obtener una cadena que incluya la fecha y la hora en 1/1000 segundos.

Puedes comenzar 8 tareas incluso en un segundo y cada una obtiene su propio valor aleatorio.

El número aleatorio es generado por la tarea principal y se envía como parámetro.

 setlocal EnableDelayedExpansion for /L %%n in (1 1 8) DO start task.bat !random! 

Si necesita en su tarea.bat también un generador aleatorio independiente, puede usar el parámetro como prefijo de inicialización.

task.bat

 setlocal EnableDelayedExpansion set seed=%1 for /L %%n in ( 1 1 %seed%) do set dummy=!random! 

$RANDOM in bash no tiene este defecto.

 @echo win=%RANDOM% @for /f %%i in ('bash -c "echo $RANDOM"') do @set VAR=%%i @echo cygwin=%VAR% 

imprime los mismos números de Windows pero distintos cygwin cuando ejecuto start randomcheck.bat & start randomcheck.bat . Usar cygwin es mejor que escribir un VBScript por separado para un solo comando. Puede ser que me enseñes cómo reemplazar el bash -c "echo $RANDOM" con la alternativa del servidor de secuencias de comandos de Windows, lo que eliminaría la necesidad de instalar el cygwin.