# Solved: Random Letter Generator - Questions



## scrfix (May 3, 2009)

I am curious as to why this script works. I found a little bit of the code on another website and modified it however not sure why this actually works:


```
@echo off
setLocal EnableDelayedExpansion
set str=AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz.-_
set /a P=!random!%%55
set str=!str:~%P%,1!
echo %str%
EndLocal DisableDelayedExpansion
```
*setLocal EnableDelayedExpansion:* This is normally set to allow escaped characters more than once however this code has no escaped characters yet when I remove it the code states I am missing an operator. This means that it must be due to the following lines ie. the !'s in those lines and because of the /a. Do we need this because we are using the !!'s? Why?

*set /a P=!random!%%55:* If I read this correctly it says set P to 55 and then execute a random number. Now I believe this means execute a random number from 1 - 55 (in which case I found a more efficient method of writing my random number generator in the previous thread and it answers my question as to whether a %random% or in this case !random! number will generate a number lower than 1. My belief and I could be wrong is that the %random% variable will generate a number between 1 - 65535 based upon the time. However that is just a guess.). I just need confirmation that I am correct or wrong about this line. If I am wrong please let me know what this really says. For this particular line, is there any different to me writing this: *set /a P=%random%%%55*
It works either way. Is there any benefit to the *set /a P=!random!%%55*?

*set str=!str:~%P%,1!:* Now, this line to me says take the string. AaBbCc, etc. and start out at the nth character and display just 1 character. It is in between the !! because we are utilizing a variable within a variable so it waits until the end of the line before it executes. Okay, this I understand. I would take this out but I am sure it will help someone else. I love this place. Write out what you think things are and you just start understanding them. Once again everyone (Squashman and Jerry mainly. Thanks for your help. Two weeks ago, I would not have been able to look at this and break this down.)

*SetLocal DisableDelayedExpansion:* The original code did not have this in it. The original code worked without it however I added this and then it it added an extra blank line at the end of the return. Why?


----------



## TheOutcaste (Aug 8, 2007)

*SetLocal EnableDelayedExpansion*
This has nothing to do with escaped characters. What it does is change the way that the command interperter replaces variables with their values.
When using the percent symbol to denote a variable, the command interpreter replaces the variable name with the value of the variable when it processes the _*line*_.
When Delayed Expansion is enabled, and you use the exclamation point instead of the percent symbol, the command interpreter replaces the variable name with the value of the variable when it processes the _*command*_.
More on this later.

*set /a P=!random!%%55*
There is no need to use exclamaiont points here except for readbility, to avoid having three percent symbols in a row.
*set /a P=%random%%%55* is exactly the same in this usage.
Set /? says %random% returns a number _between_ 0 and 32767. Since it didn't say inclusive, I'm assuming it will never return 0 or 32767. Have to run a loop to test that and see if it ever does.
I'm not sure if it uses the current time for it's seed, or something else.
The reason you get the error when you remove the *SetLocal* statement is because of the exclamation points. The exclamation point is the *NOT* unary operator, unary meaning it affects the following operand. Unary operators are processed first, so they are replaced with the value of the unary operation before the rest of the line is processed, i.e., they become an operaand. 
*NOT(random)* will always be 0
So the line now looks like this:
*Set /A P=0 NOT(missing) % 55*
The missing operator is between the 0 and the next NOT. There is a 2nd error in that the 2nd NOT operator is missing an operand, but it never gets that far in it's processing to show that.
This is the same as writing 2 3 + 4. There is no operator between the first two operands.
When used with Set /A, the percent symbol is the Modulus operator. (Remember that percent symbols must be doubled when used in a batch file)
So the line is this:
*Set /A P=random Modulus 55*
A Mod B means divide A by B and return the remainder, so this line takes a random number, divides by 55, and returns the remainder. So *P* will be a number between 0 and 54, inclusive. Or 0 <= P <= 54

*set str=!str:~%P%,1!*
This is the reason for using Delayed Expansion. The command interpreter wasn't designed to handle variables inside of variables, so this won't work:
*set str=%str:~%P%,1%*
It's seen as this:
*set str=%str:~%P%,1%*
So it takes a substring of *str*, but since there is no start or length specified, it expands to the full string.
It adds the letter *P* to the string
Then it adds the variable named *,1*. Since this is most likely not defined, it's null.
So this line just adds the letter P to the end of the current value of str and you get this:
AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz.-_P
You can use this however, and delayed expansion is not needed:
*Call Set str=%%str:~%P%,1%%*
This works because of the way the command processor scans lines for variables.

The code has *Endlocal DisableDelayedExpansion* while your text has *Setlocal DisableDelayedExpansion*.
Using the enable/disable commands with EndLocal doesn't work. While this doesn't generate an error, you can't enable or disable expansion or extensions with an EndLocal statement. It doesn't seem to add an extra line for me, for I don't have an anser for that.

How delayed expansion works, and why it's needed.
The first thing the command interpreter does is to scan the line for variables. Here's a psuedocode version of what it does:


:Start
Get Line from file
Set *position* to 0
:Scan
Increment *position* by one.
If at end of line (EOL) goto endscan
If the character at *position* is the percent symbol:
Remove it* (replace with NULL), and save the position in the line as the potential start of a variable name.
:FindEnd
Increment *position* by one
If the character at *position* is whitespace or EOL:
Delete saved position and goto Done (not a variable, or it's a loop variable)
If the character at *position* is not a percent symbol, goto FindEnd
If there is no text between the saved and current position goto Done (not a variable, just a percent symbol)
Remove percent symbol, and save the position in the line as the end of the variable name.
The text *between* the saved positions is a variable name, so look it up and put it's current value into the line.
:Done

Goto Scan
:EndScan
Remove NULLs in line.
:Execute
Parse line to get command
If at EOL goto Start
If Delayed Expansion is enabled, expand variables surrounded by Exclamation points
Convert command to machine code
Execute command
Goto Execute
*This is why percent symbols must be doubled in a batch file. Why they did this differently for lines read from a file than how a line typed at the prompt is anybody's guess

The key here is that variables are expanded for the _entire_ line before the line is executed.
Example:

```
Set first=2
set second=3
set /A sum=%first% + %second%
@Echo %Sum%
```
When executed, you'll see this:

```
C:\>Set first=2

C:\>set second=3

C:\>set /A sum=2 + 3
Sum is 5
```
You'll see that first and second have been replaced with their values in the Set /A line before the line is executed. This works, because their value was set on a previous line.
If you write that as all one line though, it won't work:

```
Set first=
Set second=
Set Sum=
(Set first=2)&(Set second=3)&(Set /A sum=%first% + %second%)&@Echo Sum is %Sum%
```
Produces this:

```
C:\>Set first=

C:\>Set second=

C:\>(Set first=2 )  & (Set second=3 )  & (Set /A sum= +  )  &
Missing operand.
Sum is
```
Variables are expanded _before_ the line is executed, so when they are expanded for the Set /A statement, they haven't been assigned a value yet, as the line hasn't been executed. So an error is produced, and the numbers aren't added.
For this to work, you have to use delayed expansion:

```
SetLocal EnableDelayedExpansion
Set first=
Set second=
Set Sum=
(Set first=2)&(Set second=3)&(Set /A sum=first + second)&@Echo Sum is !Sum!
```
This works because first and second aren't replaced with their values until the Set /A command is executed, so they have been assigned values at that point.
Note that with Set /A, you don't need to use the ! symbol around the variables, expansion will be delayed automatically. You cannot use the % symbol though.

Keep in mind that a For loop, even if split across multiple lines, is still just one line (Same with a multi-line If statement), so without delayed expansion, the variables are replaced _before_ the loop executes.
This code is actually only 3 lines, not 6:

```
[COLOR=DarkRed]Set _FileNumber=0[/COLOR]
[COLOR=Blue]For /F "Tokens=*" %%I In ('dir /B') Do (
Set /A _FileNumber+=1
Echo File # %_FileNumber% is %%I
)[/COLOR]
[COLOR=Sienna]Echo Number of files is %_FileNumber%[/COLOR]
```
When the command processor gets to the For statement, it expands the variable in the Echo statement with it's current value, which is 0, so the line is actually this:

```
[COLOR=Blue]For /F "Tokens=*" %%I In ('dir /B') Do (
Set /A _FileNumber+=1
Echo File # 0 is %%I[/COLOR]
```
So it outputs 0 for each file found. It's still incremented, but it was already expanded for the Echo statement, so the chaning value will not be displayed.
Using Delayed Expansion and !_Filenumber!, the variable won't be expanded until the Echo statement is executed. The increment from the previous command will have been done, so it will display the proper number.
You do not need to use !_Filename! for the last statement, since it is outside the loop. It will use the value of _FileNumber _after_ the For statement ends.

Jerry


----------



## scrfix (May 3, 2009)

Jerry,

Thank you for taking the time. I understand Delayed Expansion quite well I believe at this point and now I have a reference if I need to check it out again because I have questions.

Excellent psuedo as well. I followed most of it. I will have to reread it when I am more awake. 1:35am here.


----------

