How to reference a variable that consists of metavariables, which require delayed expansion, to create a one-dimensional array in Batch

Multi tool use
How to reference a variable that consists of metavariables, which require delayed expansion, to create a one-dimensional array in Batch
OS: Windows 10, Version: 10.0.17134.112
I was trying to reference a variable that consists of a dynamic variable, which requires delayed expansion, in the form of !string!variable!!
. The problem I'm facing is it is being evaluated as [!string!][variable!!]
rather than ![string][!variable!]!
!string!variable!!
[!string!][variable!!]
![string][!variable!]!
Here's an example of what I'm trying to do:
@echo off
setlocal EnableDelayedExpansion
::Sets variables - Items to be excluded, and an initial index number to reference these variables
set "_elem1=ex1"
set "_elem2=ex2"
set "_elem3=ex3"
set "_n=1"
::Builds a one-dimensional array excluding some items where item-set is arranged in a specific order
for %%e in (A B C ex1 D E F ex2 G H ex3 I J) do (
if not "!_elem!_n!!"=="%%e" (
set "_array=!_array!!_spc!"%%e""
set "_spc= "
) else (
set /a "_n+=1"
)
)
::Displays actual output
echo %_array%
::Displays DESIRED output
echo "A" "B" "C" "D" "E" "F" "G" "H" "I" "J"
pause
Basically, what it does, or at least what it is supposed to do, is it builds a filtered one-dimensional array of items from for
item-set by running a conditional block. If "!_elem!_n!!"
is not equal to "%%e"
, the result for the current iteration is appended to the stored value of _array
variable. Otherwise, it ignores the value from the current iteration and increments the index by 1, effectively changing the value of !_elem!_n!!
on the next iteration.
for
"!_elem!_n!!"
"%%e"
_array
!_elem!_n!!
The problem is variable _n
is dynamic and requires delayed expansion. What I'm trying to accomplish is for the variable !_elem!_n!!
to be evaluated as !
_elem
!_n!
!
rather than !_elem!
_n!!
_n
!_elem!_n!!
!
_elem
!_n!
!
!_elem!
_n!!
Upon researching, I've stumbled upon these sources:
The answers provided in the link are really valuable but the index used in the examples are not dynamic, and so, !_elem%_n%!
would be fine in those situations but not if the index is dynamic. Also, a method using call
was provided, which is clever, but call
does not work with if
s.
!_elem%_n%!
call
call
if
I'm running out of ideas here. I'd really appreciate any ideas you could throw at this.
Thank you all very much!!
for
for %%f in ("!_n!") do if not "!_elem%%~f!"=="%%e" ( ... )
3 Answers
3
Your approach had the flaw that you didn't compare each _elem
with each %%e
_elem
%%e
Found
%%e
_elem
for /l
_elem
Found
:: Q:Test201873SO_51147577.cmd
@echo off & setlocal EnableDelayedExpansion
::Sets variables - Items to be excluded, and an initial index number to reference these variables
set "_elem1=ex1"
set "_elem2=ex2"
set "_elem3=ex3"
set "_spc="
::Builds a one-dimensional array excluding some items where item-set is arranged in a specific order
for %%e in (A B C ex1 D E F ex2 G H ex3 I J) do (
Set "Found="
For /l %%n in (1,1,3) do (
if "!_elem%%n!"=="%%e" Set Found=true
)
if Not defined Found (
set "_array=!_array!!_spc!"%%e""
set "_spc= "
)
)
::Displays actual output
echo %_array%
::Displays DESIRED output
echo "A" "B" "C" "D" "E" "F" "G" "H" "I" "J"
pause
Sample output:
> SO_51147577.cmd
"A" "B" "C" "D" "E" "F" "G" "H" "I" "J"
"A" "B" "C" "D" "E" "F" "G" "H" "I" "J"
Drücken Sie eine beliebige Taste . . .
Brilliant!! FOR /L makes it possible to compare elements to be excluded for each iteration of FOR item-set, which removes the complication (or flaw) of FOR item-set being in a specific order. Thank you very much for your help!! I really appreciate it..
– SpaghettiCode
Jul 3 at 7:35
I was thinking about a very minute detail. What if we add an
else
clause to the second if
block and re-position set "found="
there, so that it only clears when necessary? What do you think?– SpaghettiCode
Jul 3 at 20:24
else
if
set "found="
You want to check every entry in %%e separately if it has a match in any _elem, so the reset is necessary at the begin of the next entry.
– LotPings
Jul 3 at 20:28
The solution is to use another for loop to gain another layer of variable expansion:
for %%e in (A B C ex1 D E F ex2 G H ex3 I J) do (
for %%f in ("!_n!") do if not "!_elem%%~f!"=="%%~e" (
set "_array=!_array!!_spc!"%%~e""
set "_spc= "
) else (
set /A "_n+=1"
)
)
See also this post: Arrays, linked lists and other data structures in cmd.exe (batch) script.
An alternative is to move the if block into a sub-routine (let us call it :SUB
). Therein you can use immediate expansion:
:SUB
for %%e in (A B C ex1 D E F ex2 G H ex3 I J) do (
call :SUB _n "%%~e"
)
:: ...
goto :EOF
:SUB
set "index=%~1"
if not "!_elem%index%!"=="%~2" (
set "_array=!_array!%_spc%"%~2""
set "_spc= "
) else (
set /A "%index%+=1"
)
goto :EOF
I did not analyse the logic of your script -- refer to LotPings' answer for that...
This deserves an upvote because this is the answer I was looking for, at least originally, and the right answer to the question I posted. But after seeing LotPing's answer, I realized it is better to compare each excluded item at each iteration, in which case the FOR item-set does not have to be in specific order. But thank you very much. Great answers, I'll keep these in mind..
– SpaghettiCode
Jul 3 at 8:08
I have to admit that I also like LotPings' answer more than mine, because he seems to think outside the box, while I was too focussed to the syntax issue...
– aschipfl
Jul 3 at 8:14
You may also use this approach: instead of define an array with the values to be excluded, you may write an array with such a values in the variable names. This makes the code simpler:
@echo off
setlocal EnableDelayedExpansion
::Sets variables - Items to be excluded as individual array elements
set "ex1=1"
set "ex2=1"
set "ex3=1"
::Builds a one-dimensional array excluding some items where item-set is arranged in a specific order
for %%e in (A B C ex1 D E F ex2 G H ex3 I J) do (
if not defined %%e (
set "_array=!_array!!_spc!"%%e""
set "_spc= "
)
)
::Displays actual output
echo %_array%
::Displays DESIRED output
echo "A" "B" "C" "D" "E" "F" "G" "H" "I" "J"
pause
Of course, this method only works when the values are valid variable names, but you may embeed most characters in a variable name enclosing them in quotes: var["most chars here"]=1
.
var["most chars here"]=1
This is by far the simplest yet the most ingenious approach I have ever seen. You're an expert as always—a Batch scripting master. I've seen your posts in the past and are all excellent source of information. I can see why you are sometimes defensive about your posts. It's all about passion. It's extremely difficult to make anything useful in Batch and it seems very easy for you. Thank you very much. I really appreciate your answer..
– SpaghettiCode
Jul 3 at 21:50
Thanks a lot for your kindly words! Do you know that you may change the selected answer if you wish?
;)
– Aacini
Jul 3 at 23:18
;)
Man, you're doing fine! The post authors should be the one begging for your valuable answers..
– SpaghettiCode
Jul 4 at 0:21
By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.
The solution is to use another
for
loop to gain another layer of variable expansion:for %%f in ("!_n!") do if not "!_elem%%~f!"=="%%e" ( ... )
; see also this post– aschipfl
Jul 3 at 6:40