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.
Related
I'm looking to use a variable which gets defined inside a for loop and use it to call a predefined variable.
For example: To start the program "Slack" I need to call the variable %Slack%.
The batch file will take your input, so If I input "Slack", the variable !filename[%%i]! will be assigned "Slack". Once that is done, I wish to call %Slack% using !filename[%%i]!. As per logic: %!filename[%%i]!%
Using %!filename[%%i]!% will make the batch file crash and close.
Setting set file=!filename[%%i]! and running it with %file% will return nothing.
Any smart trick?
#echo off
setlocal EnableDelayedExpansion
:: Predefined variables
for /f "delims== tokens=1,2" %%G in (D:\programs.txt) do set %%G=%%H
set /P "filenames=Enter programs to install: "
:: Puts input into array
set n=0
for %%a in (%filenames%) do (
set /A n+=1
set "filename[!n!]=%%~a"
)
:: Run variables imported from programs.txt
for /L %%i in (1,1,%n%) do (
:: This is where I want !filename[%%i]! to be called as %!filename[%%i]!%
)
pause
endlocal
program.txt example
Slack=C:\Users\username\AppData\Local\slack\slack.exe
Text=C:\textfile.txt
Program=C:\program.bat & C:\program_license.txt
I don't like to use the CALL hack described in the comments because 1), it is relatively slow, and 2) it causes an extra round of parsing that can require additional escaping (not an issue here). Even worse, it will corrupt quoted ^ - "^" becomes "^^", and there is no escape sequence that can prevent that.
My solution is to add an additional FOR loop so that the expansion can be staged.
for /L %%i in (1,1,%n%) do for %%A in ("!filename[%%i]!") do echo !%%~A!
This is certainly more verbose than the CALL hack, but it is a good code pattern to learn because it is more universally applicable.
Okay. I got rid of most of the useless code and have been tinkering with it here and there trying to find what is the problem and I think I finally found it:
Windows' Batch can't edit variables that will be expanded if those are inside a FOR loop.
Ex:
set /a x=1
Powershell Get-Clipboard> %temp%\ffmpeglist.txt
setlocal enableExtensions enableDelayedExpansion
for /F "delims=| tokens=*" %%A in (%temp%\ffmpeglist.txt) do (
set input[!x!]=%%A
call echo !input[%x%]!
set /a x += 1
)
endlocal
Expected behavior:
g:\videos\youtube1.mp4
g:\videos\youtube2.mp4
g:\videos\youtube3.mp4
g:\videos\youtube4.mp4
g:\videos\youtube5.mp4
What I get:
g:\videos\youtube1.mp4
g:\videos\youtube1.mp4
g:\videos\youtube1.mp4
g:\videos\youtube1.mp4
g:\videos\youtube1.mp4
No matter what I do, set /a x+= 1 will not change the value of x.
Are there solutions? I'm open to anything.
EDIT
In your heavily changed batch (in fact a new question) change
call echo !input[%x%]!
to
call echo %%input[!x!]%%
If in a (code block) you might need delayed expansion to force actual values,
if at the same time using an indexed variable you need another level of delayed expansion you can accomplish with a pseudo call and doubled percent signs
Call set Input=%%input[!x!]%%
mediainfo --Output=Video;%%Height%% !input! > %temp%\ffmpegres%x%.txt
Thanks to #Aacini and #LotPings , I've arrived at the answer:
set /a x=1
Powershell Get-Clipboard> %temp%\ffmpeglist.txt
setlocal enableDelayedExpansion
for /f "delims=|" %%A in (%temp%\ffmpeglist.txt) do (
set input[!x!]="%%A"
for %%i in (!x!) do echo !input[%%i]!
set /a x =+ 1
)
The problem wasn't that BATCH couldn't
edit variables that will be expanded if those are inside a FOR loop.
But that it does it in a not very straight forward way.
::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
)
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
)
Am trying to read characters of a string inside a for loop.
The command !string:~1,3! works fine. But can I do this with variables instead of 1 and 3. I tried the following code, but I don't know what is wrong. Its not working.
#echo off
setlocal enableextensions enabledelayedexpansion
set string=abcdefghij
set /a count=1
for /l %%x in (1,1,3) do (
set string2=!string:~%count%,1!
set /a count+=1
echo !string2!
pause
)
but It always gives the output as:
b
I want the output to be as:
b
c
d
Kindly help in solving this.. A big thanks in advance
In order to achieve what you want, you need to do a Delayed Expansion twice, that is, something like this:
set string2=!string:~!count!,1!
Of course, previous line is invalid. Although there are several ways to solve this problem, most of they use call command that is slow. To fix this problem so it run in the fastest way use a for command to change the first !count! expansion into a FOR replaceable parameter, and then use it in the original expression:
for %%i in (!count!) do set string2=!string:~%%i,1!
The problem is that the expansion of %count% isn't delayed, so it has the same value for every loop iteration. It's better written like this:
#echo off
setlocal enableextensions enabledelayedexpansion
set string=abcdefghij
set /a count=1
for /l %%x in (%count%,1,3) do (
set string2=!string:~%%x,1!
echo !string2!
)
Edit
If you want to keep %count% evaluated as the variable is set and not just at the beginning of the for loop, use Aacini's answer.