Batch Loop menu (multiple selection) - arrays

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
)

Related

Batch: using upper for loop parameter in lower one

The command im trying to run is:
::%items% is defined elsewhere and is the amount of items per line in the file
FOR /F "usebackq tokens=1-%items% delims=," %%1 IN (`TYPE %TextFile%`) DO (
FOR /l %%a in (%items%,-1,1) do (
set /a "number=%%a"
echo !number!
:: This is the main command I believe im having issues with
set word!number!=%%!number!
echo !word1!
echo !word2!
echo !word3!
)
set /a "lineused%randomline%=1"
goto exitloop
)
:exitloop
pause
Now what I'm trying to do is set the variable called wordX where X is the number of the token. Edit: Basically, trying to use the %% variabla from the upper for loop which the lower one is running inside of.
I could type all the lines of
set word1=%%1
set word2=%%2
set word3=%%3
but that would defeat the purpose of the versatile system I'm trying to build.
Format of the text file (%TextFile%) would simply be, in this case:
line1i1,line1i2
line2i1,line2i2
But I need for it to work also on for example:
line1i1,line1i2,line1i3,line1i4
line2i1,line2i2,line2i3,line1i4
Interesting idea, but that cannot work because FOR variable expansion takes place before delayed expansion. You need a method to get an extra round of FOR variable expansion.
You can CALL a subroutine, and then use a dummy FOR loop to re-establish a FOR context. FOR variables are global in scope as long as you are in a FOR loop context. So your subroutine can access a FOR variable that was defined earlier.
...
...
FOR /F "tokens=1-%items% delims=," %%1 IN ('TYPE %TextFile%') DO (
FOR /l %%a in (%items%,-1,1) do call :set %%a
echo !word1!
echo !word2!
echo !word3!
)
...
...
exit /b
:set
for %%. in (.) do set "word%1=%%%1"
exit /b
The above works, but I don't like it because CALLs are expensive (slow). This is typically not a problem when you only have a few CALLs. But in this case the CALL is in a tight loop - one for every column times the number of rows in the file. Ouch!
If you really want to parametize your SET statements, and you want decent performance, then you can define a dynamic "macro". Simply store the needed commands in a variable, and then execute the content of the variable within your loop.
Also note that the above is limited to 9 items (10 if you start with 0 instead of 1). It is easy to extend the supported item count to 26 if you use letters, and a lookup string.
Finally, your dynamic FOR is within some parenthesized block. Presumably your ITEMS is defined outside the block, otherwise %items% could not be used in the FOR /F definition. The SET macro must be expanded using regular expansion, so it should be defined at the same time ITEMS is defined - outside the outer loop.
set /a items=3
::Define SET macro
set "v= ABCDEFGHIJKLMNOPQRSTUVWXYZ"
set "set="
for /l %%N in (1 1 %items%) do set "set=!set!&set "word%%N=%%!v:~%%N,1!""
set "set=!set:~1!"
FOR ... some loop ... DO (
...
...
FOR /F "tokens=1-%items% delims=," %%A IN ('TYPE %TextFile%') DO (
%set%
echo !word1!
echo !word2!
echo !word3!
)
...
...
)
If the ITEMS variable must be set within the outer loop, then you must CALL out of the loop to establish the inner FOR /F loop.
Note that you dont need the TYPE to get contents of file, the FOR command itself can iterate over file content
You could try something like this:
#echo off
setlocal enabledelayedexpansion
set "TextFile=textfile.txt"
set /a "lineNum=0"
set /a "i=0"
for /f "tokens=*" %%a in (%TextFile%) do (
set /a lineNum=!line_num! + 1
set "line=%%a"
for %%b in ("!line:,=" "!") do (
set /a "i=!i!+1"
set /a "wordNum=!lineNum! * !i!
set "word!wordNum!=%%b"
)
)
echo !word1!
echo !word2!
echo !word3!
Given a file with the contents:
aaa,bbb,ccc,ddd
eee,fff,ggg,hhh
Output will be:
"aaa"
"bbb"
... and so on
To remove the quotes, use %%~b at the inner FOR at set "word!wordNum! line.
Also note that you don't even need to define the number of items per line!
Hope it helps,
Cheers!

set /A command expands with a comma for more than one var, only set does not

(set /a "m1=1,m2=2")
for /f %%c in ("%m1%%m2%") do echo %%c
pause
The brackets else where than due to the for command are used in cases, a space key should have been added.
The echo of the for command, is 12. I used the number characters to face the set /A command with decimal Expression.
When i try the same procedure only with a set for a Shell, may also be named m1 it is just possible without comma seperation.
With set command the m1 Expression would be 1 m2 2 and not two values like with a set /A SET.
Is there a way to use set only once and not only with the set /A?
As other answers and comments already indicated, there is no way to directly do this in one command, but via a procedure. The method below is the simplest one:
#echo off
rem Define the several values
set "vars=m1=1,m2=2"
rem Do it:
set "%vars:,=" & set "%"
echo m1=%m1%
echo m2=%m2%
You may remove the #echo off command and execute this program to see what exactly is executed...
As I understand the question you want something like:
set x=1,y=2
and as a result to have two variables (like set /a). The answer is no.
Though you can iterate trough expressions with plain for :
#echo off
for %%a in (
"x=1" "a=5"
"y=2" "b=6"
"z=3" "c=7"
) do set "%%~a"
echo %x% %y% %z% %a% %b% %c%
Mind that the quotes around the items are mandatory because = is a delimiter. You can put everything on line and to use as separators , ,; ,<space>
May be with a lot of variables this can save you from some writing...?
this can be rewritten like this:
#echo off
set "vars=x=1,y=2,z=3,a=5,b=6,c=7"
for %%a in ("%vars:,=","%") do set "%%~a"
echo %x% %y% %z% %a% %b% %c%
And thus you'll need to change only the vars value.

How to increment for loop variable in batch script?

::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
)

How to split string with "=" without for loop in batch file

I would like split a string in two part with = as delimiter.
I saw this post but I do not manage ta adapt.
I try this:
set "str=4567=abcde"
echo %str%
set "var1=%str:^=="^&REM #%
echo var1=%var1%
Why it does not work?
While not a bulletproof solution (use the for, artoon), without more info, this can do the work
#echo off
setlocal enableextensions enabledelayedexpansion
set "str=4567=abcde"
rem Step 1 - remove the left part
set "str1=!str:%str%!"
rem Step 2 - Get the right part
set "right=!str:*%str1%!"
rem Step 3 - Get the left part
set "left=!str:%right%=!"
set "left=%left:~0,-1%"
echo [%left%] [%right%]
edited to adapt to comments (OP code in comments adapted to my code, or the reverse)
for /f "delims=" %%i in ('set') do (
setlocal enabledelayedexpansion
rem Step 1 - remove the left part
set "str=%%i"
for %%x in ("!str!") do set "str1=!str:%%~x!"
rem Step 2 - Get the right part
for %%x in ("!str1!") do set "right=!str:*%%~x!"
rem Step 3 - Get the left part
for %%x in ("!right!") do set "left=!str:%%~x=!"
set "left=!left:~0,-1!"
echo [!left!] [!right!]
endlocal
)
And no, as previously indicated this is not bulletproof and some of the variables show problems (had I said it is not bulletproof?).
What i don't understand is the requirement to not use a for loop and then use a for loop. It is a lot easier this way
for /f "tokens=1,* delims==" %%a in ('set') do (
echo [%%a] [%%b]
)
Another alternative (not as easy as the for, more stable than the previous one, non bulletproof) is
for /f %%a in ('set') do (
call :split left right %%a
echo [!left!] [!right!]
)
goto :eof
:split leftVar rightVar data
set "%~1=%~3"
setlocal enabledelayedexpansion
set "data=%*"
set "data=!data:*%1 %2 %3=!"
set "data=%data:~1%"
endlocal & set "%~2=%data%"
goto :eof
As npocmaka commented above, = has special meaning and cannot be replaced with traditional variable string manipulation. If you know the length of either side of the equal sign, you could strip off a number of characters. For example, if "4567" will always be 4 characters, you could set "var1=%str:~0,4%". Or if "abcde" will always be 5 characters, you could set "var1=%str:~0,-6%" (5 chars + 1 for the equal sign).
Otherwise, a for loop is your only other option without using 3rd party utilities.
for /f "delims==" %%I in ("%str%") do set "var1=%%I"
If you've got grep installed, you can do something like:
echo %str% | grep -P -o "^[^=]*"
... but you'd still need to capture its output with another for /f loop.
If you are allergic to for loops, and as an exercise in providing a solution to your question without any regard for efficiency, here's how you get the first half of your string without using a single for loop. Put grep and its dependencies in your %PATH%. Then:
echo %str% | grep -P -o "^[^=]*" >temp.txt
set /P "var1="<temp.txt
del temp.txt
echo %var1%
There, I fixed it!

Batch - Read contents of a file in an array

I've a text file with two rows (say param.txt) which is shown below:
Mar2012
dim1,dim2,dim3,dim4
I want to read this file in batch and store the contents of first line in a variable called cube_name. When I'm reading the second line, I want to split the comma delimited string dim1,dim2,dim3,dim4 and create an array of four elements. I am planning to use the variable and the array in later part of the script.
The code which I created is shown below. The code is not working as expected.
#echo off & setlocal enableextensions enabledelayedexpansion
set /a count_=0
for /f "tokens=*" %%a in ('type param.txt') do (
set /a count_+=1
set my_arr[!count_!]=%%a
)
set /a count=0
for %%i in (%my_arr%) do (
set /a count+=1
if !count! EQU 1 (
set cube_name=%%i
)
if !count! GTR 1 (
set dim_arr=%%i:#=,%
)
)
for %%i in (%dim_arr%) do (
echo %%i
)
echo !cube_name!
I get to see the following when I run the code:
C:\Working folder>test2.bat
ECHO is off.
So this doesn't appear to work and I can't figure out what I'm doing wrong. I am fairly new to the batch scripting so help is appreciated
Your first FOR loop is OK. It is not how I would do it, but it works. Everything after that is a mess. It looks like you think arrays are a formal concept in batch, when they are not. It is possible to work with variables in a way that looks reminiscent of arrays. But true arrays do not exist within batch.
You use %my_arr% as if it is an array, but my_arr is not even defined. You have defined variables my_arr[1] amd my_arr[2] - the brackets and number are part of the variable name.
It also looks like you have a misunderstanding of FOR loops. I suggest you carefully read the FOR documentation (type HELP FOR from a command line). Also look at examples on this and other sites. The FOR command is very complicated because it has many variations that look similar to the untrained eye, yet have profoundly different behaviors. One excellent resource to help your understanding is http://judago.webs.com/batchforloops.htm
Assuming the file always has exactly 2 lines, I would solve your problem like so
#echo off
setlocal enableDelayedExpansion
set dimCnt=0
<param.txt (
set /p "cube_name=" >nul
set /p "dimList=" >nul
for %%D in (!dimList!) do (
set /a dimCnt+=1
set "dim[!dimCnt!]=%%D"
)
)
echo cube_name=!cube_name!
for /l %%I in (1 1 !dimCnt!) do echo dim[%%I]=!dim[%%I]!
One nice feature of the above solution is it allows for a varying number of terms in the list of dimensions in the 2nd line. It will fail if there are tabs, spaces, semicolon, equal, * or ? in the dimension names. There are relatively simple ways to get around this limitation if need be.
Tabs, spaces, semicolon and equal can be handled by using search and replace to enclose each term in quotes.
for %%D in ("!dimList:,=","!") do (
set /a dimCnt+=1
set "dim[!dimCnt!]=%%~D"
)
I won't post the full solution here since it is not likely to be needed. But handling * and/or ? would require replacing the commas with a new-line character and switching to a FOR /F statement.
I'm impressed of your code!
Do you try to debug or echo anything there?
You could simply add some echo's to see why your code can't work.
#echo off & setlocal enableextensions enabledelayedexpansion
set /a count_=0
for /f "tokens=*" %%a in ('type param.txt') do (
set /a count_+=1
set my_arr[!count_!]=%%a
)
echo ### show the variable(s) beginning with my_arr...
set my_arr
echo Part 2----
set /a count=0
echo The value of my_arr is "%my_arr%"
for %%i in (%my_arr%) do (
set /a count+=1
echo ## Count=!count!, content is %%i
if !count! EQU 1 (
set cube_name=%%i
)
if !count! GTR 1 (
echo ## Setting dim_arr to "%%i:#=,%"
set dim_arr=%%i:#=,%
echo
)
)
for %%i in (%dim_arr%) do (
echo the value of dim_arr is "%%i"
)
echo cube_name is "!cube_name!"
Output is
### show the variable(s) beginning with my_arr...
my_arr[1]=Mar2012
my_arr[2]=dim1,dim2,dim3,dim4
Part 2----
The value of my_arr is ""
cube_name is ""
As you can see your part2 fails completly.

Resources