How to increment for loop variable in batch script? - batch-file

::Compare with available valid arguments
FOR /L %%i IN (1,1,!avArgc!) DO (
FOR /L %%j IN (1,1,!argc!) DO (
IF !avArgv[%%i]!==!argv[%%j]! (
echo Match: !avArgv[%%i]!
::Check the next option
SET /A nextArg=%%j
SET /A nextArg+=1
FOR /L %%n IN (!nextArg!,1,!nextArg!) DO (
IF !nextArg! LEQ !argc! (
echo next arg: !argv[%%n]!
call :CheckSubOption
)
)
)
)
)
In my above code example - How do I take for loop variable like %%j and increment itself within the for loop like this %%j++ ? Current solution that I have (which is messy and I don't like it) is to create a new variable and set it to the value of %%j and then increment that variable and start using that variable like this:
::Check the next option
SET /A nextArg=%%j
SET /A nextArg+=1

Observing your code and your intention, it would seem that you would want to skip numbers during the loop structure. The way you want to change it though would be destabilizing. In most scripting languages such as matlab,bash, and batch, the variable that is used in for-loops serves as a frame of reference within the loop. When you tell the code to run a particular for-loop, it will run that computation regardless if the parameters of it changed. A real world example of this is the professor who is using outdated figures to solve a problem and it isnt until the next day he receives the new figures. The professor cant change his answer accordingly because he doesnt have the new data yet.
This does not mean this problem is unsolvable. In fact there are a variety of ways to approach this. The first one which is a little more complicated involves a nested For structure.
#echo off
set /p maxLength=[Hi times?]
set skip=0
FOR /L %%i IN (1,1,%maxLength%) DO (call :subroutine %%i)
echo alright im done.
pause
GOTO :eof
rem the below code uses a for loop structure that only loops 1 time based on the passed argument from the overall for loop as so to make changes to how its run.
:subroutine
set /a next=%1+%skip%
FOR /L %%r IN (%next%,1,%next%+1) DO (call :routine %%r)
GOTO :eof
:routine
if %1==3 (set /a skip=1)
echo %skip%
echo %next%
echo %1
pause
GOTO :eof
When running the program, the variable next will skip the value of 3 if the maxlength variable is greater than 3.
The reason this is so is because the nested for-loop only runs once
per iteration of the overall for loop
. This gives the program time to reset the data it uses, thanks to the call command which serves as a way to update the variables. This however is extremely inefficient and can be done in much less lines of code.
The second example uses GOTO's and if statements.
#echo off
set jump=1
:heyman
set /A "x+=%jump%"
if %x%==4 (set /A "jump=2")
echo %x%
if %x% LSS 10 goto heyman
echo done!
This code will essentially echo the value of x thats incremented each time until it reaches the value of 10. However when it reaches 4, the increment increases by 1 so each time it runs the loop increments the x value by 2. From what you wanted, you wanted to be able to change the way the value of %%j increments, which can not be done as %%j is a statement of where the for-loop is in its computation. There is no difference in what can be accomplished with for-loops and goto statements except in how they are handled.
While i unfortunately don't have the correct form of your code yet, i know that code examples i have given can be utilized to achieve your particular desire.

The general solution for thoses case is to not rely on blocks inside loops/if but instead to use subroutines where you are not blocked by the level of evaluation.
FOR /L %%i IN (1,1,!avArgc!) DO call :Loop1 %%i
goto :EOF
:Loop1
FOR /L %%j IN (1,1,!argc!) DO call :Loop2 %1 %%j
goto :EOF
:Loop2
IF !avArgv[%1]!==!argv[%2]! (
echo Match: !avArgv[%1]!
::Check the next option
SET /A nextArg=%2+1
call :CheckOpt %nextArg%
)
goto :EOF
:CheckOpt
IF %1 LEQ %argc% (
echo next arg: !argv[%1]!
call :CheckSubOption
)

Related

Sub-stringing a text file with variable parameters is not working

I am trying to substring a string from a text file stored in a variable "user" each loop the substring parameters is increment so that i can process next data. However, the variable parameter "Inc" that i am giving to the sub-string command doesn't seem to update its value within the for loop.
I already used the SETLOCAL EnableDelayedExpansion but with no success. Below is piece of the code:
Note: "Inc" is defined previously as integer and have initial value of 21.
I expect that each loop the variable 'test' should be: substring of "user" 41,20 .. 61,20 .. 81,20 .. and so on.. But the problem is that for the all 12 loops "Inc" in the substring command keeps the value 41 and never increases even though the echo Inc command below shows that it does increase.
Appreciate your help in this issue.
:setfunc
echo. >>parsed.txt
IF !inew!==48 (
for /L %%g IN (1,1,12) do (
Set test=!user:~%Inc%,20!
echo !test!
echo !user:~%Inc%,20! >>parsed.txt
Set /a Inc=!Inc!+20
echo count=%%g
echo Inc=!Inc!
)
The problem is the immediate %-expansion you are using for a variable (Inc) whose value is changing in the same block of code, namely the for /L loop.
To solve this you need another layer of variable expansion. There are the following options:
Add another for loop and use its meta-variable expansion, which happens before delayed expansion:
> "parsed.txt" (
for /L %%g in (1,1,12) do (
for %%i in (!Inc!) do (
echo(!user:~%%i,20!
set /A "Inc+=20"
)
)
)
This is the preferred method in my opinion as it is the fastest one.
Use call, which initiates another %-expansion that happens after delayed expansion:
> "parsed.txt" (
for /L %%g in (1,1,12) do (
call echo(%%user:~!Inc!,20%%
set /A "Inc+=20"
)
)
The doubled %-signs are needed to escape the first %-expansion phase and literally leave % behind.
Since Inc is purely numeric (or more precisely, a signed 32-bit integer), you could also use for /F together with set /A:
> "parsed.txt" (
for /L %%g in (1,1,12) do (
for /F %%i in ('set /A "Inc"') do (
echo(!user:~%%i,20!
set /A "Inc+=20"
)
)
)
This works since set /A is executed in cmd-context rather than in batch-context when run by for /F, where it returns its (final) result, which in turn is then captured by for /F.
This is not a quite practical approach in this situation here, though it might be considered when there is a for /F loop involved anyway.

Batch file quitting after doing "call :loop"

I have a bit of (Bad) Code for encrypting text, but to be able to decrypt it needs to have something inbetween the numbers. I want to fit random letters inbetween the numbers so it looks less obvious, this is where i got to:
#echo off
setlocal enableDelayedExpansion
set /p code=Text:
set chars=0123456789abcdefghijklmnopqrstuvwxyz
for /L %%N in (10 1 36) do (
for /F %%C in ("!chars:~%%N,1!") do (
Set _Alphanumeric=ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
Set _count=0
set _RNDLen=%random%
Set /A _RNDLen=_RNDLen%%4
If !_count! leq %_RNDLen% call :loop
set "code=!code:%%C=-%%N!"
)
)
echo !code!
echo !_str!
pause
:loop
Set /a _count+=1
set _RND=%random%
Set /A _RND=_RND%%51
SET _str=!_str!!_Alphanumeric:~%_RND%,1!
EXIT /B %ERRORLEVEL%
The problem is that the program just quits before giving any output, even if i remove the exit /b statement. Thanks for help
I've no idea what principle you're using for your algorithm, but fundamentally you need to understand delayed expansion.
When your outer loop, for %%N is parsed, every %var% is replaced by the contents of that variable at that time, hence
set _RNDLen=%random%
If !_count! leq %_RNDLen% call :loop
are replaed by
set _RNDLen=a specific random number
If !_count! leq call :loop
The first line here will set _rndlen to the same number every time (for ny run) and since _rndlen is undefined at the start of the loop, it willl be replaced by nothing, hence the if statement has faulty syntax and hence cmd objects and would display a message.
You can use !random! with delayed expansion invoked to select a rendom number each time, and you need !_rndlen! to access the changed value of _rndlen (changed from its original value of nothing to some random value and then mod-4'd)
Personally, I'd assign _alphanumeric outside of the (outer) loop since its value isn't varied by the loop operation.
And naturally, you know that when you hit Return following the pause, the loop code will be executed before the routine terminates (by flow-through) and you should include a
goto :eof
line after the pause to skip this last operation.

Batch nested variables

So, I have some nested variables in Batch.
It's causing me an issue.
The reason I have nested variables is because I am emulating an array via:
array[0]=1
array[1]=2
array[2]=3
etc
Now, I have a counter counter
I want to be able to do %array[!counter!]%, and the output would equal !counter! + 1.
But I can't.
I think it's because having it surrounded in %'s makes Windows try to expand the variable ASAP, and it does, breaking the counter.
So, instead, why not do !array[!counter!]!? Well, I tried this, and I think that, instead of interpreting it as (array[(counter)]), where ()'s are used to show what !!'s are holding, Windows instead interprets it as (array[)counter(), which is useless to me.
Keep in mind: Whenever I use !!'s, assume I have done setlocal EnableDelayedExpansion -- I just don't include it as that would be a pain for both me and readers.
Any ideas how to fix this?
(at least) Two possible ways. The first is faster and more verbose - CALL command hits the performance.
#echo off
setlocal enableDelayedExpansion
set array[0]=1
set array[1]=2
set array[2]=3
set counter=0
echo first way :
for /l %%# in (1;1;3) do (
for /f %%$ in ("!counter!") do echo !array[%%$]!
set /a counter=counter+1
)
set counter=0
echo second way :
for /l %%# in (1;1;3) do (
call :subr1 !counter!
set /a counter=counter+1
)
goto :eof
:subr1
echo !array[%1]!
goto :eof
This is what I ended up doing:
for /l %%a in (1;1;3) do (
echo !array[%%a]!
)
Originally, I was using a manual counter via variable counter, but using for /l means I can have a counter without having to set it to a variable, and, more importantly, not calling it like !varname!; instead like %%varname, eliminating confusion.

Batch: Is there a way to set it up so that if at any point in time any variable equals something it does a command?

Basically what my title says.
Is there a way to set it up so that if at any point in time any variable equals something it does a command?
Without knowing what you hope to accomplish, it's difficult to know what will work for you. However, I have thought of a potential solution. For the variable you want to watch, instead of simply calling set "varname=value", you could call :set varname value, then use a subroutine to perform the watching. Here's an example:
#echo off
setlocal
set /a foo = 10, loop = 1
:loop
call :set foo "%foo% * %loop%"
set /a loop += 1
goto loop
:: End main script / begin :set subroutine
:set <var_to_set> <expression>
setlocal
set /a test = %~2
:: watch for %foo% to have a value greater than 50000
if %test% geq 50000 (
echo %~1 equals %test%. Press any key to exit.
>NUL pause
exit
)
endlocal & set "%~1=%test%"
goto :EOF
Of course this is much less efficient than a for /L loop, but it should demonstrate the proof of concept. The :set subroutine effectively watches %foo% to reach a value, then performs an action.
See this page on endlocal and this page on DOS functions for more examples of using subroutines to set variable values.

Batch Loop menu (multiple selection)

I'm trying to set up a batch menu that allows for multiple selection at once then runs all the functions. Sequence that functions are not relevant just the fact that the functions will be run with out, outside errors. Here is the code that I have so far.
#Echo off
Echo Please Enter the corrasponding numbers separated by a space or colon (,)
Echo for the Options that you would like to run e.g. 1 4,3 2
Echo Option #1
Echo Option #2
Echo Option #3
Echo Option #4
Echo.
SET /P Selection=Please Select Restore Options?
echo You chose: %Selection%
setlocal ENABLEDELAYEDEXPANSION
Set /a index = 0
FOR %%A IN (%Selection%) DO (
SET Array[!index!] = %%A
SET /a index += 1
)
for /F "tokens=2 delims==" %%s in ('set Array[') DO (
set string=%%s
set string=%string: =%
echo %string%
Call :Opt%string%
)
pause
goto :EOF
:Opt1
ECHO Option 1's code
GOTO :EOF
:Opt2
ECHO Option 2's code
GOTO :EOF
:Opt3
ECHO Option 3's code
GOTO :EOF
:Opt4
ECHO Option 4's code
GOTO :EOF
The code I have works to the point where trying to call the Array veriable and attach it to a Call e.g. Call :Opt%%s
The probelm I have is that the array variable keeps coming out with a space proceeding the selected variable. So I have tried combating this with set string=%string:=% but I keep getting an error.
Error :
either echo is off and only opt is getting called with out the selected variable.
Help with this would be amazing, Thanks in advance.
The start of the problems is
SET Array[!index!] = %%A
------------------^-^---- = aditional spaces
This aditional spaces are used, so you end with a variable with an aditional space in its name and an aditional space in its value. So, better use
SET "Array[!index!]=%%A"
The reason for the echo error is you forget to use delayed expansion in the for %%s loop. You change the %string% variable inside the loop and try to use the changed value inside the same loop.
for /F "tokens=2 delims==" %%s in ('set Array[') DO (
set "string=%%s"
set "string=!string: =!"
echo !string!
Call :Opt!string!
)
But the corrections indicated in the original set make it unnecessary to replace the spaces.
MC ND solved most of the problems with your code.
One trivial issue - the punctuation is a comma, not a colon ;-)
But a more serious issue, what if the user entered 3 choices, and there already was a variable named Array[4]? It would run that extra value that hadn't been entered by the user. It would even attempt to run a value stored in Array[anythingGoes.
You've got the number of values stored in "index", so why not use it? A more common and simpler way to iterate the array is to use a FOR /L loop. This also preserves the original order. Your way would change the order once you get 10 or more entries. (I know you say order doesn't matter, but why change the order if you don't have to?)
setlocal enableDelayedExpansion
for /l %%N in (1 1 %index%) do (
echo !Array[%%N]!
call :Opt!Array[%%N]!
)
But I don't see a reason to mess with an array at all. Your loop that parses the user input could simply call the functions directly. Now you don't even need delayed expansion.
for %%A in (%Selection%) do (
echo %%A
call :Opt%%A
)

Resources