2

How to make this script work under command prompt?, right now all random number are the same

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
1

3 Answers 3

7

Wow - you have lots of problems with that small snippet of code :-)

Your posted code is missing the SET /A option. I'm assuming your actual code has it.

The reason your code fails with a syntax error is because the command within the FOR IN() clause is executed via a cmd /C yourCommandHere command. When the implicit cmd /C command is parsed, it treats = as a token delimiter unless it is escaped or quoted. Any consecutive string of token delimiters is converted into a single <space> before your command is actually executed in a new CMD thread using command line semantics. The list of token delimiters is , ; = <space> <non-breaking space> and <tab>.

So quoting the command will eliminate the syntax error:

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

As will escaping the =:

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

But you don't really need to assign the random number to a variable. The FOR IN() command is executed within a command line context, and SET /A will print the computed value to stdout when run within a command line context. So the following also eliminates any syntax error with effectively the same results:

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

Here is a simpler method to give a result from 1 to 5 (random mod 5 + 1):

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

But I seriously doubt any of the above fixes give your desired result.

There is something very peculiar going on with the value of %RANDOM%. Your use of %%RANDOM%% properly causes the expression to be evaluated each iteration. But for some reason, the random number is nearly constant for any given run. Once in a while, one of the iterations will vary, but for most runs, each iteration gets a constant value. I think it must have something to do with the random number generator seed value. Perhaps the random number generator is initiated with a seed value each time a CMD session starts, and the seed value is only changing very slowly. Remember that the FOR IN() clause is executed in a new CMD session.

Here is a test program that demonstrates that the %%test%% is properly getting re-evaluated each iteration. It also shows how %%random%% is remaining nearly constant within a run.

@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
)

Here is the output from 2 runs of the above code. Note how the 1st run has one variation in the random number. The 2nd run has a constant random value.

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

There really is no reason to put the random number computation within a FOR /F IN('command') clause. Everything is much simpler if you use SET /A with delayed expansion directly within your outer loop.

I believe the following may be what you are looking for:

@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]!
)

Here is some sample output:

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

EDIT

Here is better evidence that the randomizer for CMD session is reseeded, and the seed changes only slowly.

@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"

--OUTPUT--

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
3
  • So delayed expansion is not the solution, but you used it in your solution. Got it. Apr 27, 2013 at 19:44
  • @PatrickS. - The lack of delayed expansion in the OP's original code was not the source of the problems, as I tried to explain in the middle of my answer. In theory it should have worked without delayed expansion, but some oddity of the random number generation results in a nearly constant value, even though it is re-evaluated each iteration. My 2nd to last code proves %%RANDOM%% is getting re-evaluated properly. In order to get around the problems, I restructured the code in a way that does require delayed expansion.
    – dbenham
    Apr 28, 2013 at 4:37
  • @PatrickS - The original code was only part of a much larger complex scripts, which is build intend to clarify some questions. Which actually consist of many problems altogether. 1. The delayed expansion of %random%. 2. The nested behavior of cmd/c in a for loop 3. The integer division of the random variable, which actually cause problems later on 4. The nested nature of %random% resolve at different level of nesting. That's why dbenham solution is more completed and explained.
    – user218473
    Apr 29, 2013 at 17:46
3

Since you are expanding the %RANDOM% variable in a FOR loop, you need to use delayed expansion.

This produces 5 echos of the same number:

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

This produces 5 "random" numbers:

setlocal ENABLEDELAYEDEXPANSION

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

You can find more information about delayed expansion by running

SET /?
4
  • i need that from 1 to 5 only ... that's why i need a SET statement
    – user218473
    Apr 27, 2013 at 12:45
  • 2
    You can do that with the SET you already had. I was just demonstrating delayed expansion. Your set command would be something like: SET /A RND=!RANDOM!*5/32768+1 Apr 27, 2013 at 12:52
  • 1
    i guess i have try my solution out, but really need some explanation that why the = sign need to be escaped with ^ ?? I cannot found documents saying that anywhere...
    – user218473
    Apr 27, 2013 at 13:12
  • Delayed expansion is often the solution to many problems. But not so in this case. The problem(s) with the OP's code is actually fiendishly complicated and subtle. See my answer
    – dbenham
    Apr 27, 2013 at 15:53
2

In this case you need 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"
)

This is faster than %%test%%. Output example:

0 26542
1 32475
2 25609
3 4495
4 6719

This also works (slower):

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"
)

But the following doesn't work:

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
1
  • 1
    The delayed expansion in you 2nd to last code is occurring within the main batch file, and it works fine there. The delayed expansion in your last code is occurring in the new implicit CMD session, where it fails to properly give a random number. But it is not truly constant; it occasionally gives multiple values within one run, as evidenced in my answer. This is totally unexpected and fascinating behavior.
    – dbenham
    Apr 28, 2013 at 4:45

You must log in to answer this question.