3

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!]!

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.

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!!

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 ifs.

I'm running out of ideas here. I'd really appreciate any ideas you could throw at this.

Thank you all very much!!

SpaghettiCode
  • 317
  • 2
  • 14
  • 2
    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](https://stackoverflow.com/a/10167990) – aschipfl Jul 03 '18 at 06:40

3 Answers3

3

Your approach had the flaw that you didn't compare each _elem with each %%e

  • In this batch the var Found is a flag to indicate if the current %%e was matched by any _elem
  • a counting for /l is used to iterate the _elem variables.
  • The content of the var Found doesn't matter it's existence isn't influenced by being set in the same (code block).

:: Q:\Test\2018\07\03\SO_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 . . .
  • 1
    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 03 '18 at 07: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 03 '18 at 20:24
  • 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. –  Jul 03 '18 at 20:28
2

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:

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...

aschipfl
  • 33,626
  • 12
  • 54
  • 99
  • 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 03 '18 at 08: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 03 '18 at 08:14
1

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.

Aacini
  • 65,180
  • 12
  • 72
  • 108
  • 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 03 '18 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 03 '18 at 23:18
  • Man, you're doing fine! The post authors should be the one begging for your valuable answers.. – SpaghettiCode Jul 04 '18 at 00:21