# Solved: windows batch file, for loop, variable delimiters



## skippyVon (May 24, 2011)

I'm building this batch file using Windows 7 and I'd like to make the FOR loop process a variable number of tokens. I'll use lower case chars a-to-z for each loop variable. I have a routine to count the delimiters in the input string. I'd like to use that value to process specific tokens - relative to the number of tokens found. For example if 11 tokens are found I may want to process only tokens 8, 9 and 10. For that example I'd want to process the variables "%%h", "%%i" and "%%j". I have a "function" for converting a value to the char however I don't know if this level of expansion is possible. I'd like to do the following within the for loop: take a value and convert it into an ascii char and then use that result as the for loop variable name.
Here is the test code:

```
@echo off
setlocal ENABLEDELAYEDEXPANSION
set testStr="../skooby/doo dd/george town/files/make/this/a/really/long/pathname/da.log"
echo input string: %testStr%
set /a delimCnt=11

FOR /F "usebackq tokens=1-%delimCnt% delims=/" %%a IN ('%testStr%') DO (
 	echo a: %%a 
 	echo b: %%b
 	
	set /a asciiCtr=97
	set asciiChar=
	call :convertValToAsci asciiCtr asciiChar
	echo asciiChar:!asciiChar!
	REM the next line I would like to echo the loop variable "%%a"
	echo %%!asciiChar!
)
goto :veryEnd
REM ================================================
REM Convert input decimal value to ascii
REM 
REM usage: 1st parm = input value, 2nd parm = output variable
:convertValToAsci
set /a valIn=%1
cmd /c exit /b %valIn%
set %2=%=ExitCodeAscii%  
goto :EOF
REM ================================================

:veryEnd
endlocal
echo Done
```


----------



## Squashman (Apr 4, 2003)

I am not following your logic at all.

If you want to process only tokens 8-9 then you need to have a variable for your starting and ending Token. You only have a variable for your ending token.

Also, if your Tokens options says *Tokens=8-9* those tokens get assigned to %%a %%b & %%c. Not %%h, %%i, & %%j


----------



## skippyVon (May 24, 2011)

This code is a stripped down version to provide a proof-of-concept.
In this example the token range is from 1 to 11 because "delimCnt" is set to 11.
And the FOR loop variable sequence is started at "%%a".
So the 1st token parsed would be referenced using %%a. The second with %%b and the 8th, 9th, 10th would be h, i and j respectively.

Please refer to the code taking into account my statements regarding the purpose. Maybe I should have emphasized the word "may" in my original post. The logic for determining which tokens will be processed is not the point. I'm using a variable that would give me a reference - total number of delimiters. That part is done. But now I'd like to use that reference point in a manner where I could generate the FOR-loop variable(s) desired. 

Another example: suppose I wanted to only process the next-to-last token. And each input string has a variable number of tokens. In the posted example that means I'd want to process the "%%j" token. Whereas in a string containing only 5 tokens I'd process the "%%d" token (%%e would be the last token in a 5 token sequence). I'd always start the FOR-loop variable sequence with %%a to provide a maximum number of 26 possible tokens. But the big problem is - can I generate those variable names (letters) on the fly and use them to dereference the FOR-loop variables. Probably can't...

I hope this is more clear.


----------



## Squashman (Apr 4, 2003)

skippyVon said:


> Another example: suppose I wanted to only process the next-to-last token. And each input string has a variable number of tokens. In the posted example that means I'd want to process the "%%j" token. Whereas in a string containing only 5 tokens I'd process the "%%d" token (%%e would be the last token in a 5 token sequence). I'd always start the FOR-loop variable sequence with %%a to provide a maximum number of 26 possible tokens. But the big problem is - can I generate those variable names (letters) on the fly and use them to dereference the FOR-loop variables. Probably can't...
> 
> I hope this is more clear.


Not really but lets start with smaller steps here.

In your description above you are saying the string may have a variable number of tokens within the string after it is undelimited. But, since it is variable you won't know how many their are so your *TOKENS* option will initially have to be *TOKENS=1-26*.

So within your first For Loop you will need to determine how many tokens are actually populated because if there are only 20 tokens and you want to process token 19 you will need to determine how many there actually are. In order to figure out how many tokens there are you will need some way to determine if they are populated and then increment a counter everytime one of them is. To determine if the tokens are populated you will need to use an *IF DEFINED* statement on each variable (%%a through %%z).

Do you agree with this logic and I am understanding your intentions?


----------



## skippyVon (May 24, 2011)

I agree with your logic but the problem is after that point. I didn't include the functionality for determining the number of tokens because I was trying to streamline the problem.
Consider the number of tokens in the variable-length-input-string to have already been calculated.
The FOR loop will use this variable to cycle through all the existing tokens. In the code I called this variable "delimCnt".

```
FOR /F "usebackq tokens=1-%delimCnt% delims=/" %%a IN ('%testStr%') DO (
```
But the question boils down to this: Can one generate a FOR-loop variable *from within the loop* and dereference it?
So from the perspective of *within the loop*, can I say "I know there is a %%c token can I do something like the following?"

```
set myTempChar = getCharSymbol(int valForChar_C)  REM Obviously this statement is pseudo code
  REM use myTempChar as a FOR-loop variable
  echo %%!myTempChar !
```
I've tried various versions of the last line to no avail.


----------



## Squashman (Apr 4, 2003)

You can create as many For loops as you want. You can nest them if you want but in this case you don't even need to nest them.

One for Loop to determine the number of delimiters.
A second for loop that doesn't need to be nested to do your processing on the same variable but this time you know what you want your starting and stopping tokens are.


----------



## Squashman (Apr 4, 2003)

This code will get the number of tokens in the variable.

```
@echo off & setLocal EnableDELAYedExpansion
set str=../skooby/doo dd/george town/files/make/this/a/really/long/pathname/da.log
set tstr=%str%
set delim=/
set tcount=1
:loop
if !tstr:~0^,1! equ !delim! (
set /a tcount+=1
)
if "!tstr:~1!" neq "" (
set tstr=!tstr:~1!
goto :loop
)
echo %tcount%
```


----------



## skippyVon (May 24, 2011)

Squashman, I thank you for your patience and apologize for my inability to describe my question adequately. And I thank you for the code you posted - I will examine it in time. But we're still talking "past each other". Determining the number of tokens is something I have - and omitted from the first post. And the question is not regarding nested loops.
It is all about generating the name of a loop variable from within the loop and dereferencing it. 
If you run the code in my initial post, look at the echoed line prior to "Done". It prints "%a" on my box but that is not what I want. This line is #16 in the bat file.
Whereas line #8 in the bat file truly echoes the a-th variable. That being the first token of *"..* 
So what I tried to do was make line 16 functionally equivalent to line 8. I generated the ascii char "a" and tried to dereference it as the loop-variable "%%a".
It probably can't be done. As for the reasoning of why I'm doing it this way or what is the end goal - think of this as just an exercise in "what can and cannot be done when referencing loop-variables".
I believe now that it cannot be done in this manner.


----------



## Squashman (Apr 4, 2003)

So it looks like you are tying to DOUBLE expand a variable. But I don't see how you are every assigning anything to the variable *asciiChar*.

There is a reason you need delayed exapnsion because the For loop immediately populates all the variables with values when they are not surrounded by exclamation.

My basic understanding of what you are trying to do which makes absolutely no sense to me is randomly assign the token you want to parse.


----------



## skippyVon (May 24, 2011)

> DOUBLE expand


 - yes that is a good description.
*asciiChar *is assigned via the "convertValToAsci" function. Refer to line #26 in the bat file.
I don't like the use of the "exit" command in that function - but it works. Try setting *asciiCtr *to 98 on line #11 and run it. Then *asciiChar *will receive the 'b' character.
As for your statement regarding "randomly assign" - there is no randomness. I'm using (or trying to) an integer representing the index of the token to process. In this case processing the token is merely an "echo" of it.


----------



## Squashman (Apr 4, 2003)

I guess I am not understand why you want to take a number and call it an ASCII value to return the corresponding letter. If you want to process Token a then process token a. Why are you making all this convoluted code? Are you just doing this to see if you can do it. Otherwise it seems like you are on a path of insanity!


----------



## Squashman (Apr 4, 2003)

I finally understand what you are trying to do but I think your first assumption was correct. You can't do that kind of variable expansion.


----------



## Squashman (Apr 4, 2003)

I think what you would have to do is assign all your Tokens to specific variables then you can use delayed expansion

set _a=%%a
set _b=%%b
etc...etc..

Then I believe you can use delayed expansion to do what you are trying to.


----------



## skippyVon (May 24, 2011)

I guess I'd have to know the mechanics of how the scripts are processed to truly understand why I can't generate a variable name and then access the internally generated variable using my name. According to the documentation, if a For-loop has tokens=1-10 and the first one is %%a then the variables %%a thru %%j are generated for us. Whether we use them or not. But I'd have to know more details of how/when the variables are expanded to understand why this method doesn't work.
As for my particular goals that led me to this idea - there are other solutions, but none that would be as slick if I could get this idea to work (IMO).
And since no one else has chimed in yet (and many have viewed it) I think we are in agreement in proclaiming this idea as *undoable*.
So I'll mark it as "solved" and let it be.
Thanks for your help, Squashman.


----------



## Squashman (Apr 4, 2003)

All percent variables are expanded immediately. That is why there is the delayed expansion option. Your problem isn't really delayed expansion though. You are essentially only assigning the string %%a to your variable. You are not assigning the value of %%a to your variable. 

The loop variables are immediately expanded when the loop executes. So you basically are strike 2 and your out.


----------



## skippyVon (May 24, 2011)

My original goal was to access individual tokens from a string using an integer index. After organizing the working pieces of code into functions, it became obvious that my "index-translation" piece did not need to be done from within a For-loop. Just within the function for retrieving the token. Now I can call that function from within a loop and retrieve every other token, every third token or just specific tokens. And I don't have to declare/test every possible loop-variable to determine if it exists. I just use an index within the range.
So if anyone else needs something similar here is my latest working iteration.

```
@echo off
setlocal ENABLEDELAYEDEXPANSION

set testStr="/skooby/doo dd/george town/files/make/this/some/really/long/pathname/for/you/da.log/"

echo input string: %testStr%

set totalNumTokens=

call :countTokens %testStr% / totalNumTokens

echo Total number of tokens: %totalNumTokens%

REM Placeholder for some algorithm that incorporates total number 
REM of tokens in its calculation to determine the desired token index
REM
REM NOTE - index is limited to the set (1-26)
set /a specificIndex = 3

echo retrieving token at index %specificIndex%
set returnedToken=
call :getToken %testStr% %totalNumTokens% %specificIndex% returnedToken

echo Token: %returnedToken%

goto :veryEnd

REM ================================================
REM ================================================
REM Returns token from input string using index
REM LIMIT: this function can only process a string
REM    containing a maximum number of 26 tokens
REM 
REM usage: 1st parm = input string
REM        2nd parm = total # of tokens
REM        3rd parm = index of desired token
REM        4th parm = output token
:getToken

REM echo input parms for getToken: %1 %2 %3 %4

REM Add 97 to map the value (of 1 to 26) to the ascii 
REM set of chars 'a' to 'z'
set /a tokenIndexDesired=%3+96

set tokenIndexChr=
call :convertValToAsci %tokenIndexDesired% tokenIndexChr

REM echo tokenIndexChr: %tokenIndexChr%

set stringIn=%1

REM Remove the quote chars 
set stringIn=%stringIn:"=%

FOR /F "usebackq tokens=1-%2 delims=/" %%a IN ('%stringIn%') DO (
	set %4=%%%tokenIndexChr%
)

goto :EOF
REM ================================================
REM ================================================
REM  Count the tokens in input string using the specified
REM  delimiter
REM
REM  usage: 1st parm = input string
REM         2nd parm = delimiter char
REM         3rd parm = output number tokens

:countTokens

set inStr=%1
set delimChr=%2

REM Determine if they are delimiters in the first and/or
REM last positions and adjust the token count accordingly
set /a endCnt=0

REM Remove the quote chars 
set tmpString=%inStr:"=%

REM test 1st char for delimiter
set strChr=%tmpString:~0,1%

if %strChr% == %delimChr% set /a endCnt=1

REM test last char for delimiter 
set strChr=%tmpString:~-1%

if %strChr% equ %delimChr% set /a endCnt+=1

set /a tokenCnt=0
if %endCnt% equ 0 set /a tokenCnt=1
if %endCnt% equ 2 set /a tokenCnt=-1

:funcLoop
REM echo in funcLoop inStr is !inStr!

if !inStr:~0^,1! equ %delimChr% (
   set /a tokenCnt+=1
)

if "!inStr:~1!" neq "" (
   set inStr=!inStr:~1!
   goto :funcLoop
)

set /a %3=!tokenCnt!

goto :EOF
REM ================================================
REM ================================================
REM Convert input decimal value to ascii
REM 
REM usage: 1st parm = input value, 2nd parm = output variable
:convertValToAsci

set /a valIn=%1

cmd /c exit /b %valIn%

set %2=%=ExitCodeAscii%  

goto :EOF
REM ================================================

:veryEnd

endlocal

echo Done
```


----------



## Squashman (Apr 4, 2003)

I am glad you got it figured out. Will have to look at your code more in depth when I have time.

Can't say I have ever had a need to do what you are doing but maybe some day.


----------

