3

This code

@echo off
setlocal EnableDelayedExpansion

set myvar=first
set first=second

echo myvar:!myvar!
set myvar=!myvar!
echo myvar:!myvar!

gives

myvar:first
myvar:first

on Windows Vista SP2.

The output I had expected is

myvar:first
myvar:second

Why the difference and how to obtain desired effect?

Piotr Dobrogost
  • 38,049
  • 34
  • 218
  • 341
  • The problem is that `set myvar=!myvar!` expands to `set myvar=first`, you set it with the same content, and then you ask `echo myvar:!myvar!` to show the content of myvar – jeb Oct 25 '11 at 08:31
  • @jeb Well, You are totally right. How could I have missed this... Could you please provide this as an answer (possibly adding any additional information You find useful)? – Piotr Dobrogost Oct 25 '11 at 20:38

3 Answers3

9

The problem is that set myvar=!myvar! expands to set myvar=first,
you set it with the same content, and then you ask echo myvar:!myvar! to show the content of myvar.

I will try to add some more explanations, even if Aacini and shf301 already answered the question.

Both showed the double expansion with the !%var%! construct, and Aacini explained why it can work, and why the reversed version %!var!% can't work.

IMHO there are four different expansions.
Delayed Expansion:
As Aacini explained the delayed expansion is safe against any special characters in the content (it can handle ALL characters from 0x01 to 0xFF).

Percent Expansion:
The percent expansion can't handle or removes some characters (even with escaping).
It can be useful for simple content, as it can expand variables after an endlocal barrier.

setlocal
set "myVar=simple content"
(
endlocal
set result=%myVar%
)

FOR-Loop-Parameters expansion:
It is safe, if the delayed expansion is disabled, else the delayed expansion phase is executed after the expansion of the %%a variables.
It can be useful, as it can expand variables after an endlocal barrier

setlocal EnableDelayedExpansion
set "var=complex content &<>!"
for /F "delims=" %%A in ("!var!") DO (
  endlocal
  set "result=%%A"
)

SET Expansion:
set var expands also a variable, and it is always safe and works independent of the delayed expansion mode.

Aacini just explained how the call %%%var%%% construct work, I only want to give some additional remarks.
call is stackable, you can use many of them and each restarts the parser.

set "var=%%var%%#"
call call call call call echo %var%

results to %var%######

But call have many disadvantages/side effects!
Each call double all carets ^
You can say: "Hey I've tested it and I can't see any doubling"

call call call call echo ^^

result ^

Nevertheless it's true, but it's mostly hidden, as each restart also have a special character phase where carets escapes the next character, but you can see the doubling effect with

call call call call echo "^^"^^

result "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^"^

Even if a call expansion restarts the parser, you can never use the delayed expansion in any phase (only in the first one).

call stops working if it detects unescaped special characters.

echo you ^& me
call echo you & me
call echo you ^& me
call echo you ^^& me
call echo you ^^^& me

Only the first results to the output you & me, all the others fails.

Another problem is that call is extremly slow, a call set var=content is ~50times slower than set var=content, the cause is that call try to start an external program.

@echo off
setlocal
(
   echo echo *** External batch, parameters are '%%*'
) > set.bat
set "var="
call set var=hello
set var

I hope it was interesting a bit ...
And if you want to go more in depth you can read CALL me, or better avoid call
and How does the Windows Command Interpreter (CMD.EXE) parse scripts?

Community
  • 1
  • 1
jeb
  • 70,992
  • 15
  • 159
  • 202
  • +1, Good description. Just one example more: `set myvar=first` `set first=second` `set second=third` `call call set myvar=%%%%%%%myvar%%%%%%%` `echo %myvar%` display: `third` – Aacini Oct 26 '11 at 04:31
  • 1
    1) `SET VAR` is convenient, but risky. If both VAR and VAR2 are defined, then SET VAR will give both definitions. 2) I think you can add a 5th level: `SET /A` expansion of variables without % or !, as long as the content is a number. – dbenham Nov 19 '11 at 06:06
  • Of course, you are right. 'set's is only a good way to see the real content in any situation, but I never use it for the program, only for debugging – jeb Nov 19 '11 at 10:04
2

This problem is not directly related to Delayed variable Expansion, but to the fact that two value expansions are required: the first one give the variable name and the second one must replace this name by its value. The direct way to do that is via two expansions in the same line as shown in the previous answer: set myvar=!%myvar%! that works because %var% expansion is done before the command-line is analyzed for execution whereas !var! expansion is done later, just before the command is executed (hence the "delayed" name). This mean that %var% expansion may provide parts of the command and may cause syntax errors, but !var! not. For example if %var%==value ... cause an error if var is empty or have spaces, but if !var!==value ... never cause a syntax error.

The double expansion of values may be achieved in other ways that does not involve Delayed variable Expansion. For example, we may create an auxiliary Batch file that do the second expansion:

echo myvar:%myvar%
echo set myvar=%%%myvar%%%> auxiliary.bat
call auxiliary
echo myvar:%myvar%

Previous method may be used to do a third or even deeper level expansions, and even be combined with Delayed Expansions to create very complex value managements. This matter is not just a curiosity, but the key to access array elements or linked lists. For example:

set month[1]=January
set month[2]=February
. . .
set month[12]=December
for /f "tokens=1-3 delims=/" %%a in ("%date%") do echo Today is !month[%%a]! %%b, %%c
Aacini
  • 59,374
  • 12
  • 63
  • 94
  • +1, Nice explanation, only the external call sample is a bit courious, it's simpler with `call set myvar=%%%myvar%%%` – jeb Oct 25 '11 at 08:27
  • @jeb: Yes, you are right, but is harder to understand that `call` creates a new context (in the same line) where the second expansion is achieved. With the auxiliary Batch file this is very clear, but your suggestion is better to write real code. +1! – Aacini Oct 25 '11 at 18:37
  • @jeb It's the first time I see `call set...`. Could You elaborate? – Piotr Dobrogost Oct 25 '11 at 20:06
  • `call set myvar=%%%myvar%%%` is executed this way: 1. Replace %% by % and %myvar% by its value resulting in `call set myvar=%first%` 2. The CALL command is executed so it executes in turn its command, but in a new context where it is re-processed as if it were just read from the Batch file. 3. The `set myvar=%first%` is executed in the usual way: replace %first% by its value (second) and assign it to myvar: `set myvar=second`. – Aacini Oct 25 '11 at 20:37
1

What you're trying to do won't work - delayed expansion only changes the variable expansion behavior of a variable inside of a block. It doesn't allow you the aliasing/nesting (for a lack of a better word) that you are attempting.

set myvar=first sets the variable myvar to the text "first". set first=second sets the variable first to the text "second. There is no link between those two lines. myvar will never evaluate to something that it wasn't explicitly set to.

I don't believe there is anyway to accomplish what you are trying to do here.


* Edit *

OK after taking a look at your answer I seeing how that works, you can get your desired output with this:

@echo off
setlocal EnableDelayedExpansion

set myvar=first
set first=second

echo myvar:%myvar%
set myvar=!%myvar%!
echo myvar:%myvar%

So the magic seems to happen because of the way that standard and delayed expansion occur. The line set myvar=!%myvar%! is seems be expanded first by the standard expander to set myvar=!first! (you'll see this if you run the script with echo on). Then the delayed expander runs and expands !first to "second" and set's myvar to that.

I have no idea if this is documented behavior as to how standard and delayed expansion should work or just an implementation detail (which means it could break in the future)

Community
  • 1
  • 1
shf301
  • 30,022
  • 2
  • 46
  • 83
  • *`myvar` will never evaluate to something that it wasn't explicitly set to*. It's being set to new value explicitly in the line `set myvar=!myvar!`. See my [answer](http://stackoverflow.com/questions/1199931/#7868477) for comparison. – Piotr Dobrogost Oct 24 '11 at 22:43
  • And in your example in this question !myvar! will always expand to first. In your other example the line `set %1=!var!` is expanded to `set myvar=!var!` (run it with echo on) so it's still being explicit set in that example. BTW that's a cool example, I never had any idea you combine normal and delayed expansion like that. – shf301 Oct 24 '11 at 22:53
  • Thanks. Btw, maybe You could answer [How to escape variables with parentheses inside if-clause in a batch file?](http://superuser.com/questions/348458/) question? Returning to this question; What's the difference between `set myvar=!myvar!` from the script in this question and the `set myvar=!var!` (which You say is the result of `%1=!var!`) from the other question? I dont see any. To me they should work the same; right side is evaluated and set as a value of variable denoted by the left side. – Piotr Dobrogost Oct 24 '11 at 23:10
  • They are the same, there just a set statement. It's the double expansion of `!%var%!` in the other question ad !%myvar%! that are moving the values around. – shf301 Oct 24 '11 at 23:17
  • I guess so because this was the only way to get this other example work :) But I wonder **why**? I mean delayed expansion (operator `!`) is the **only** thing one should need to be able to read freely *nested* value, right? – Piotr Dobrogost Oct 24 '11 at 23:25
  • No, delayed expansion does no nesting. It's the double expansion with both '!' and '%' that is doing the nesting. – shf301 Oct 24 '11 at 23:26
  • The nesting may be achieved in separate lines even with % only. See my answer. – Aacini Oct 25 '11 at 01:22