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:
Code:
Set first=2
set second=3
set /A sum=%first% + %second%
@Echo %Sum%
When executed, you'll see this:
Code:
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:
Code:
Set first=
Set second=
Set Sum=
(Set first=2)&(Set second=3)&(Set /A sum=%first% + %second%)&@Echo Sum is %Sum%
Produces this:
Code:
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:
Code:
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:
Code:
Set _FileNumber=0
For /F "Tokens=*" %%I In ('dir /B') Do (
Set /A _FileNumber+=1
Echo File # %_FileNumber% is %%I
)
Echo Number of files is %_FileNumber% 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:
Code:
For /F "Tokens=*" %%I In ('dir /B') Do (
Set /A _FileNumber+=1
Echo File # 0 is %%I 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