An example of extreme dynamic variable nesting.

Discussion forum for all Windows batch related topics.

Moderator: DosItHelp

Post Reply
Message
Author
orange_batch
Expert
Posts: 442
Joined: 01 Aug 2010 17:13
Location: Canadian Pacific
Contact:

An example of extreme dynamic variable nesting.

#1 Post by orange_batch » 26 Nov 2010 00:45

Very rarely is it necessary to use more than one nested dynamic variable in referencing another. I just happened to require this now, so I'll write about it. (Edit: Turns out I didn't require it, but anyways...)

Even if you read once that it involves nestling a call within a call (within a call... and so on), an explanation and an example might not have been provided. So I'll do that.

First the explanation:

Everybody knows, for one level using enabledelayedexpansion, you write:

Code: Select all

set number=1
set log1=Hello
echo:!log%number%!

Forget this for now, I'll get back to it further on.

Let's say we have set var=hello
Every %% expands to %. Then every %var% expands to hello
So,
%var% expands to hello
%%var%% expands to %var%
%%%var%%% expands to %hello%
%%%%var%%%% expands to %%var%%
%%%%%var%%%%% expands to %%hello%%
%%%%%%var%%%%%% expands to %%%var%%%
...and so on.

Each time you use call, it will expand on top of Command Prompt's expansion.
So,
call %var% expands to hello expands to hello (does nothing)
call %%var%% expands to %var% expands to hello
call %%%var%%% expands to %hello% expands to nothing (variable hello doesn't exist)
call %%%%var%%%% expands to %%var%% expands to %var%
call %%%%%var%%%%% expands to %%hello%% expands to %hello%
call %%%%%%var%%%%%% expands to %%%var%%% expands to %hello%
...and...
call %%%%%%%var%%%%%%% expands to %%%hello%%% expands to %% (variable hello doesn't exist)
...and so on.

"Ok, get on to the call nestling. How do you figure out how many % signs to write?"

It's easy, the formula is:
Where n is the nested level,
2^n = Number of % signs on each side.

So starting from the Command Prompt level, take a look at this:

Code: Select all

@echo off

:: Level 0 (2^0=1. One % sign.)
set a_cup=pot
echo:%a_cup%

:: Level 1 (2^1=2. Two % signs.)
set a_pot=box
call echo:%%a_%a_cup%%%

:: Level 2 (2^2=4. Four % signs.)
set a_box=crate
call call echo:%%%%a_%%a_%a_cup%%%%%%%

:: Level 3 (2^3=8. Eight % signs.)
set a_crate=boat
call call call echo:%%%%%%%%a_%%%%a_%%a_%a_cup%%%%%%%%%%%%%%%

:: Level 4 (2^4=16. Sixteen % signs.)
set a_boat=Hello World
call call call call echo:%%%%%%%%%%%%%%%%a_%%%%%%%%a_%%%%a_%%a_%a_cup%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

So, about enabledelayedexpansion, you can do an extra level of nestling to remove one of the calls. If you're using enabledelayedexpansion, this is probably faster than using an extra call, so I would recommend it. You might wonder how this effects the 2^n formula. Well, you're just pushing the calls forward by one expansion, so subtract 1 from each level (except Level 0 stays 0). Level 1 becomes 0, Level 2 becomes 1, etc... 2^(n-1)

Code: Select all

@echo off&setlocal enabledelayedexpansion

set a_cup=pot
echo:%a_cup%

set a_pot=box
echo:!a_%a_cup%!

set a_box=crate
call echo:%%a_!a_%a_cup%!%%

set a_crate=boat
call call echo:%%%%a_%%a_!a_%a_cup%!%%%%%%

set a_boat=Hello World
call call call echo:%%%%%%%%a_%%%%a_%%a_!a_%a_cup%!%%%%%%%%%%%%%%
Last edited by orange_batch on 12 Dec 2011 14:34, edited 7 times in total.

jeb
Expert
Posts: 1055
Joined: 30 Aug 2007 08:05
Location: Germany, Bochum

Re: An example of extreme dynamic variable nesting.

#2 Post by jeb » 26 Nov 2010 04:08

Ok, nice :)

A call is time intensive (nearly 20times slower than delayed expansion),
and it have some nice unexpected side effects.

And do you can explain this :?:

Code: Select all

@echo off
setlocal EnableDelayedExpansion
set "singleCaret=^"
set "^=One caret"
set "^^=Two carets"
call echo 1: %%!singleCaret!%%
cmd /c echo 2: %%!singleCaret!%%

--- output ---
1: Two carets
2: One caret


And how to display the string "One & two" with a call (without quotes)?

call echo one ^& two --- doesn't work

Waiting for a nice answer :)
jeb

orange_batch
Expert
Posts: 442
Joined: 01 Aug 2010 17:13
Location: Canadian Pacific
Contact:

Re: An example of extreme dynamic variable nesting.

#3 Post by orange_batch » 26 Nov 2010 05:26

Hmm I don't think I'll bother with the carets question lol, I remember that from before.

You can do this for "one & two" though:

Code: Select all

set "test=one ^& two"
call echo:%%test%%

Must be an interpreter phase issue.

This works too:

Code: Select all

set "test=one & two"
set "test=%test:&=^&%"
call echo:%%test%%

jeb
Expert
Posts: 1055
Joined: 30 Aug 2007 08:05
Location: Germany, Bochum

Re: An example of extreme dynamic variable nesting.

#4 Post by jeb » 26 Nov 2010 11:46

Yes, it works if you put and escape the content in a variable.

But it is not possible to build a string with special characters and make a call, the expansion have to be on the last call level.

All these variants will fail

Code: Select all

call echo 0 one & two
call echo 1 one ^& two
call echo 2 one ^^& two
call echo 3 one ^^^& two

Because all carets are doubled by a call, so the parser see

Code: Select all

echo 0 one    & two
echo 1 one & two
echo 2 one ^^     & two
echo 3 one ^^& two

At 0 and 2 the parser splits the line into two command, before the call is executed
If the char "&" or "|" is recognized in a call the complete cmd will be canceled.

Redirections will be ignored, the redirection destination is removed from the line.

Code: Select all

set "var=hallo > aa bb cc"
call echo %%var%%
---- OUTPUT
hallo  bb cc


jeb

Liviu
Expert
Posts: 470
Joined: 13 Jan 2012 21:24

Re: An example of extreme dynamic variable nesting.

#5 Post by Liviu » 25 Jan 2012 18:37

jeb wrote:But it is not possible to build a string with special characters and make a call, the expansion have to be on the last call level.
...
Because all carets are doubled by a call, so the parser see
...
If the char "&" or "|" is recognized in a call the complete cmd will be canceled.
...
Redirections will be ignored, the redirection destination is removed from the line.

Code: Select all

set "var=hallo > aa bb cc"
call echo %%var%%

---- OUTPUT
hallo  bb cc

jeb

Happened upon this old thread now. Thanks for summarizing the difficulties. So, if 'call' won't work, maybe we shouldn't use 'call'. But then, of course, control may not ever return. Which suggested the following trick, basically create a nested call to actually invoke the target command. The following appears to work on my xp.sp3.

Code: Select all

@echo off
setlocal disableDelayedExpansion

set "var=hallo > aa ^| bb ^^< cc ^^^& dd"
call :exec echo var
call :exec echo var

endlocal
goto :eof

:exec
setlocal enableDelayedExpansion
set "exec=%1 !%~2!"
!exec!
endlocal
goto :eof

---- Output
hallo > aa ^| bb ^^< cc ^^^& dd
hallo > aa ^| bb ^^< cc ^^^& dd

There is an additional wrinkle about the :exec itself, to be posted separately once I work out a minimal example.

Liviu

Ed Dyreen
Expert
Posts: 1569
Joined: 16 May 2011 08:21
Location: Flanders(Belgium)
Contact:

Re: An example of extreme dynamic variable nesting.

#6 Post by Ed Dyreen » 25 Jan 2012 18:58

'
Liviu wrote:

Code: Select all

@echo off
setlocal disableDelayedExpansion

set "var=hallo > aa ^| bb ^^< cc ^^^& dd"
call :exec echo var
call :exec echo var

endlocal
goto :eof

:exec
setlocal enableDelayedExpansion
set "exec=%1 !%~2!"
!exec!
endlocal
goto :eof

---- Output
hallo > aa ^| bb ^^< cc ^^^& dd
hallo > aa ^| bb ^^< cc ^^^& dd
I still like jeb's way, it produce same result :)

Code: Select all

@echo off &setlocal enableDelayedExpansion

set "var=hallo > aa ^| bb ^^< cc ^^^& dd"
echo.!"!!var!!"!

pause

Code: Select all

hallo > aa ^| bb ^^< cc ^^^& dd
Druk op een toets om door te gaan. . .
viewtopic.php?f=3&t=2593&p=11790&hilit=virtual+quote#p11790

Liviu
Expert
Posts: 470
Joined: 13 Jan 2012 21:24

Re: An example of extreme dynamic variable nesting.

#7 Post by Liviu » 25 Jan 2012 19:52

Ed Dyreen wrote:'
I still like jeb's way, it produce same result :)

Code: Select all

@echo off &setlocal enableDelayedExpansion

set "var=hallo > aa ^| bb ^^< cc ^^^& dd"
echo.!"!!var!!"!

Thanks for the pointer, though, unless I am missing something, so does "echo !var!" alone in this snippet. And, if you replace "echo" with a "call echo" it will still fail.

To put 2+2 together between this and my other recent thread (counting arguments), what I was actually looking for was a way to (a) capture the unparsed command line, and (b) be able to pass it along to external commands.

Think I am getting close, below is a proof of concept "reecho.cmd". First argument is a repeat count, then it calls itself recursively the given number of times, and echo's the command line. Everything past the first argument is passed through unparsed, including odd characters and what cmd considers delimiters.

Code: Select all

@echo off
setlocal disableDelayedExpansion

goto :getArg1
@rem inside a nested setlocal enableDelayedExpansion
:main

echo %~n0 { !args! }

set /a rearg1 = %~1 - 1
if %rearg1% gtr 0 (
  set "reargs=%rearg1%!args:~%arg1len%!"
  call :exec %%0 reargs
)

echo ..%~n0 %~1 done
goto :eof

:exec
setlocal enableDelayedExpansion
set "exec=%1 !%~2!"
!exec!
endlocal
goto :eof

::.............................................................................

:getArg1
set "remArgs=%temp%\%random%.tmp"
set prompt=@
>"%remArgs%" (
  echo on
  for %%a in (%%a) do rem { %* }
  @echo off
)
call :getFileSize "%remArgs%" sizeOld
set /a argsLen = %sizeOld% - 14
>>"%remArgs%" (
  echo on
  for %%a in (%%a) do rem { %1 }
  @echo off
)
call :getFileSize "%remArgs%" sizeNew
set /a arg1len = %sizeNew% - %sizeOld% - 14
prompt

setlocal enableDelayedExpansion
<"%remArgs%" (
  set /p "args="
  set /p "args="
  set "args=!args:~7,-3!"
)
del "%remArgs%"

goto :main

:getFileSize
set /a %~2 = %~z1
goto :eof


Test run results...

Code: Select all

C:\>reecho 4 ;,= %1 %~1 %a %%a a^|b "a|b" "a^|b" !cd! %cd^% ^<"&^"^> = ( , )) ;
reecho { 4 ;,= %1 %~1 %a %%a a|b "a|b" "a^|b" !cd! %cd% <"&^"> = ( , )) ; }
reecho { 3 ;,= %1 %~1 %a %%a a|b "a|b" "a^|b" !cd! %cd% <"&^"> = ( , )) ; }
reecho { 2 ;,= %1 %~1 %a %%a a|b "a|b" "a^|b" !cd! %cd% <"&^"> = ( , )) ; }
reecho { 1 ;,= %1 %~1 %a %%a a|b "a|b" "a^|b" !cd! %cd% <"&^"> = ( , )) ; }
..reecho 1 done
..reecho 2 done
..reecho 3 done
..reecho 4 done

Of course, I am under no illusion that there exists a perfect answer to the question, even less that'd be mine, so any and all critique/counterexamples are welcome.

Liviu

Ed Dyreen
Expert
Posts: 1569
Joined: 16 May 2011 08:21
Location: Flanders(Belgium)
Contact:

Re: An example of extreme dynamic variable nesting.

#8 Post by Ed Dyreen » 25 Jan 2012 20:07

Liviu wrote:Thanks for the pointer, though, unless I am missing something, so does "echo !var!" alone in this snippet.
Not on my Build 2600 xp sp2 :shock:

Code: Select all

@echo off &setlocal enableDelayedExpansion

set "var=hallo > aa ^| bb ^^< cc ^^^& dd"
echo.!"!!var!!"!
"echo.!var!"
pause

Code: Select all

hallo > aa ^| bb ^^< cc ^^^& dd
"echo.hallo > aa ^| bb ^^< cc ^^^& dd" wordt niet herkend als een interne
of externe opdracht, programma of batchbestand.
Druk op een toets om door te gaan. . .
I also ran your code from this thread and it also crash :?
viewtopic.php?f=3&t=2851

Liviu
Expert
Posts: 470
Joined: 13 Jan 2012 21:24

Re: An example of extreme dynamic variable nesting.

#9 Post by Liviu » 25 Jan 2012 20:16

Ed Dyreen wrote:Not on my version of XP :shock:
...
I also ran your code from this thread and it also crash :?
viewtopic.php?f=3&t=2851
Build 2600 xp sp2

Interesting... For the latter, do you mean a literal "crash", and was it with the "call :exec" (vs. "call :exec-fail") variant?

Thanks,
Liviu

EDIT: fixed misplaced quoting tags.

Post Reply