Random generator in the batch
I have a bat file
@echo %RANDOM%
and execute it using command line
start randomcheck.bat & start randomcheck.bat
Two consoles are opened and both contain the same number, 4645. This fails the purpose of random to provide different temp folders (I need random folders only when use them simulataneously). How you have normal random generator in batch?
Update https://stackoverflow.com/a/19697361/1083704 has quantified the global seed update period. The windows shell global seed is updated every second. Practically, I must add one more second for safety margin, to prevent the races and hope that this is sufficient measure. This really sucks. It means that launching 8 processes for my iCore7 will take 16 seconds. And I am still not sure that this succeeds since nothing is officially specified and it is still possible that despite 8 processes are started initially with relative time shift, it may happen that two processes finish at the same time and I must take care that they are not restarted simultaneously again. This is complete bullshit and my question was can this be cured in the batch, without resorting to C++ or VBScript?
MC ND is 100% correct on all counts within his answer, and also his follow-on comment.
Each instance of CMD.EXE initializes the random number generator upon startup using a seed that is derived from the current time with 1 second resolution. All CMD.EXE processes that launch within the same second will get identical random number sequences.
One additional aspect - the initial random number for consecutive seconds changes very slowly. It looks to me like the initial random number may actually be the seed value that was derived from the time, but I'm not sure.
EDIT - I had originally deduced all of this via experimentation. But I have since seen confirmation from an authoritative source.
Here is a script that demonstrates how the seed for CMD.EXE changes only once per second, and the seed changes very slowly:
@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
-- OUTPUT 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
This script demonstrates that the random number generator works "properly" within a single CMD.EXE process.
@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
-- OUTPUT 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
Finally, this script demonstrates how each %random%
within a given line expands to its own value, but the line values don't change between loop iterations because the looped line is only parsed once.
@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
-- OUTPUT 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
Random number generator in cmd uses the current time (second resolution) to seed the prng. So, two processes starting in the same second will generate the same "random" number.
For an option that will not collide, use the random number generator of vbscript (randomize first), or use a guid (you can generate it via uuidgen or also with vbscript), or powershell, or ....
A hybrid batch-Jscript solution
@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));
Even a perfect random number generator can (will eventually) generate collisions. A robust solution must assume collisions are possible, and compensate accordingly.
Here is one strategy that I have successfully used in the past:
Assign the unique temp file name at the top of your script. Use the %TIME%
value for a pseudo "random" number. Replace the :
with nothing to make the string valid for a file name. Two processes will only collide if they start within 1/100 seconds of each other, (assuming your processes do not run for more than a day).
You might get collisions. Collisions can be detected via a temporary lock file. Put the main body of your script in a subroutine, and call the main routine with a non-standard file handle redirected to a lock file with the "random" number - only one process can redirect output to the lock file at any given time. If a lock is detected, simply loop back and try again.
@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
It is unlikely that two processes will get the same unique name, but if they do, the second process will detect the collision, loop back and try again until it succeeds.
Uncomment the nonUnique tempBase line if you want to test the locking. Open two console windows, and launch the script in both windows. The first will successfully enter the main routine and pause. The second will loop, waiting for the first to finish. Press a key on the first, and the first will instantly end and the second will continue into the main routine.
If you want greater than 1/100 second precision, or if your processes may run longer than a day, then you should consider using WMIC OS GET LOCALDATETIME
to get a string that includes date and time to 1/1000 second.
You could start 8 tasks even in one second and each get it's own random value.
The random number is generated by the main-task and send as parameter.
setlocal EnableDelayedExpansion
for /L %%n in (1 1 8) DO start task.bat !random!
If you need in your task.bat also an independent random generator, you could use the parameter as seed prefix like.
task.bat
setlocal EnableDelayedExpansion
set seed=%1
for /L %%n in ( 1 1 %seed%) do set dummy=!random!