Batch-Script - Iterate through arguments - loops

I have a batch-script with multiple arguments. I am reading the total count of them and then run a for loop like this:
#echo off
setlocal enabledelayedexpansion
set argCount=0
for %%x in (%*) do set /A argCount+=1
echo Number of processed arguments: %argCount%
set /a counter=0
for /l %%x in (1, 1, %argCount%) do (
set /a counter=!counter!+1 )
What I want to do now, is to use my running variable (x or counter) to access the input arguments. I am thinking aobut something like this:
REM Access to %1
echo %(!counter!)
In an ideal world this line should print out my first command line argument but obviously it doesn't. I know I am doing something wrong with the % operator, but is there anyway I could access my arguments like this?
//edit: Just to make things clear - the problem is that %(!counter!) provides me with the value of the variable counter. Meaning for counter=2 it gives me 2 and not the content of %2.

#echo off
setlocal enabledelayedexpansion
set argCount=0
for %%x in (%*) do (
set /A argCount+=1
set "argVec[!argCount!]=%%~x"
)
echo Number of processed arguments: %argCount%
for /L %%i in (1,1,%argCount%) do echo %%i- "!argVec[%%i]!"
For example:
C:> test One "This is | the & second one" Third
Number of processed arguments: 3
1- "One"
2- "This is | the & second one"
3- "Third"
Another one:
C:> test One Two Three Four Five Six Seven Eight Nine Ten Eleven Twelve etc...
Number of processed arguments: 13
1- "One"
2- "Two"
3- "Three"
4- "Four"
5- "Five"
6- "Six"
7- "Seven"
8- "Eight"
9- "Nine"
10- "Ten"
11- "Eleven"
12- "Twelve"
13- "etc..."

:loop
#echo %1
shift
if not "%~1"=="" goto loop

here's one way to access the second (e.g.) argument (this can be put in a for /l loop, see below.):
#echo off
setlocal enableDelayedExpansion
set /a counter=2
call echo %%!counter!
endlocal
so:
setlocal enableDelayedExpansion
set /a counter=0
for /l %%x in (1, 1, %argCount%) do (
set /a counter=!counter!+1
call echo %%!counter!
)
endlocal

If to keep the code short rather than wise, then
for %%x in (%*) do (
echo Hey %%~x
)

#ECHO OFF
SETLOCAL
SET nparms=0
FOR /l %%i IN (1,1,20) DO (
SET myparm=%%i
CALL :setparm %*
IF DEFINED myparm SET nparms=%%i&CALL ECHO Parameter %%i=%%myparm%%
)
ECHO there were %nparms% parameters in %*
GOTO :EOF
:setparm
IF %myparm%==1 SET myparm=%1&GOTO :EOF
shift&SET /a myparm -=1&GOTO setparm
GOTO :eof
This should show how to extract random parameters by position.

For simple iteration can't we just check for additional arguments with "shift /1" at the end of the code and loop back? This will handle more than 10 arguments, upper limit not tested.
:loop
:: Your code using %1
echo %1
:: Check for further batch arguments.
shift /1
IF [%1]==[] (
goto end
) ELSE (
goto loop
)
:end
pause

Related

How to remove the nth character from a string in batch

If I happened to have a string such as hello, how would I remove the nth character from the string?I tried this, but it didn't seem to work (3 is the position):
#ECHO ON
SET "input=%~1"
CALL :a input
SETLOCAL ENABLEDELAYEDEXPANSION
:a
SET "input=!%1!"
SET /A "position=3"
FOR /L %%a IN (!position!,1,!position!) DO (
SET /A "position2=%%a+1"
FOR /L %%b IN (!position2!,1,!position2!) DO (
ECHO "!input:~0,%%a!!input:~%%b!"
)
)
ENDLOCAL
#ECHO off
SET "input=%~1"
CALL :a input
goto :EOF
:a
SETLOCAL ENABLEDELAYEDEXPANSION
SET "input=!%1!"
set /A "position=3, position2=position+1"
echo "!input:~0,%position%!!input:~%position2%!"
Output example:
C:\Users\Antonio\Documents\Tests> test.bat hello
"helo"
In your original code the string is passed in the first parameter...

Batch/cmd - subroutine does not "return" array via parameter

In the following script I want to pass a string via variable and the variable name for an array which should contain substrings to a subroutine.
The subroutine puts substrings of the passed string into an array/list which then should get "returned" by setting it as the value of the 2. passed parameter.
#ECHO OFF
SETLOCAL ENABLEEXTENSIONS ENABLEDELAYEDEXPANSION
SET testString=Hello World
REM Pass testString and substrings to subroutine
CALL :get_substrings testString substrings
REM For testing. Echo substrings. DOESN'T WORK. substrings is empty!
FOR /L %%s IN (0,1,2) DO (
ECHO !substrings[%%s]!
)
ENDLOCAL
EXIT /B 0
:get_substrings
SETLOCAL ENABLEDELAYEDEXPANSION
SET "string=!%~1!"
REM Alternative approach: Make a connection to %2 rightaway
REM SET "substrings=!%~2!"
REM Process string: Put substrings into indexed array. This works as expected!
FOR /L %%s IN (0,1,2) DO (
SET substrings[%%s]=!string:~0,5!
SET string=!string:~5!
)
REM For testing. Echo the substrings. Works as expected!
FOR /L %%s IN (0,1,2) DO (
ECHO !substrings[%%s]!
)
REM For alternative approach
REM ENDLOCAL
REM End the local the set 2.param = substringsArray
ENDLOCAL & SET %2=%substrings%
EXIT /B 0
Processing the string by creating a array with substrings in the subroutine works as expected. But setting 2. parameters value and keeping the value after subroutine doesn't work...
Notes: The processing of the string is just a dummy. The real process is slightly different but the core with the substrings array is the same. The script is executable right away.
So, how can I get the value substrings back?
This does what you want:
#ECHO OFF
SETLOCAL ENABLEEXTENSIONS ENABLEDELAYEDEXPANSION
SET testString=Hello World
REM Pass testString and substrings to subroutine
CALL :get_substrings testString substrings
REM For testing. Echo substrings.
FOR /L %%s IN (0,1,2) DO (
ECHO !substrings[%%s]!
)
ENDLOCAL
EXIT /B 0
:get_substrings
SETLOCAL ENABLEDELAYEDEXPANSION
SET "string=!%~1!"
REM Process string: Put substrings into indexed array. This works as expected!
FOR /L %%s IN (0,1,2) DO (
SET substrings[%%s]=!string:~0,5!
SET string=!string:~5!
)
REM For testing. Echo the substrings. Works as expected!
FOR /L %%s IN (0,1,2) DO (
ECHO !substrings[%%s]!
)
REM End the local the set 2.param = substringsArray
set SubEnviron=1
for /F "tokens=2* delims=[]=" %%a in ('set substrings[') do (
if defined SubEnviron ENDLOCAL
set "%2[%%a]=%%b"
)
EXIT /B 0
I wasn't able to understand your counting of characters so here's how I'd probably do it:
#Echo Off
SetLocal EnableDelayedExpansion
Set "TestString=Hello World"
For /F "Delims==" %%A In ('Set SubString[ 2^>Nul') Do Set "%%A="
Set "i=1"
Set "SubString[%i%]=%TestString: ="&Set/A i+=1&Set "SubString[!i!]=%"
Set SubString[
Pause
Example Output:
SubString[1]=Hello
SubString[2]=World
Press any key to continue . . .
For the purposes of testing you probably don't need the For loop, its purpose is to ensure there are no existing variables whose name begins with SubString[
Edit
This uses three parameters:
The string to cut%string%
A number of how long each substring should be%chrnum%
The substring parameter%strvar%
#Echo Off
SetLocal EnableDelayedExpansion
Set "string=montuewedthufrisatsun"
Set "chrnum=3"
Set "strvar=substring"
Set "i=1"
Set "_=%string%"
:Loop
Set "!strvar![%i%]=!_:~,%chrnum%!"
If "!_:~%chrnum%!"=="" GoTo Write
Set "_=!_:~%chrnum%!"
Set /A i+=1
GoTo Loop
:Write
Set !strvar![ 2>Nul
Pause
Yes, I fully understand that you are not going to change this code to PowerShell. But, it might be worth considering for the next time given how easy it is. get_substrings is a lambda.
PS C:\src\t\selarr> type .\lamb002.ps1
$teststring = 'hello cruel world'
$get_substrings = { param($t) foreach ($s in $t.split()) { $s.Substring(0,4) } }
$a = & $get_substrings $teststring
$a.length
$a[0]
$a[1]
$a[2]
PS C:\src\t\selarr> .\lamb002.ps1
3
hell
crue
worl

variable as tokens in for loop

Im trying to make a batch file that loops thru an array containing numbers like this: 1 2 3 4 5.
In the first itteration of the loop I like to pick token 1 and 2. In the second 2 and 3, in the third 3 and 4 and so on.
I do think I should use ! in the variables first and second that I use as tokens. Like in the first FOR /F, but when I do, I get: !first!" was not expected here.
And if I use %, it does not count up.
Everything works except the variable tokens. Any one knowes how to? Any help or suggestions greatly appriciated.
This is the part Im struggeling with:
setlocal EnableDelayedExpansion
set first=1
set second=2
set N=4
set output="1 2 3 4 5"
set output=%output:"=%
for /L %%a in (1,1,%N%) do (
if !counter! equ active (
set /a first+=1
set /a second+=1
)
FOR /F "tokens=!first!" %%a IN ("%output%") DO (
set nr1=%%a
)
FOR /F "tokens=%second%" %%a IN ("%output%") DO (
set nr2=%%a
)
echo nr1 var: !nr1!
echo nr2 var: !nr2!
echo counter f: !first!
echo counter s: !second!
set counter=active
)
You cannot use delayed expanded variables in the options string of for /F. Neither can you use other for variables for that. But you can use normally (immediately) expanded variables. Also you can use argument references like %1, for example.
So a nice work-around for your problem is to place the for /F loop in a sub-routine and use call in the main program with the delayed expanded variables as arguments, like this:
#echo off
setlocal EnableDelayedExpansion
set /A first=1
set /A second=2
set /A N=4
set "output=1 2 3 4 5"
set "counter="
for /L %%a in (1,1,%N%) do (
if defined counter (
set /A first+=1
set /A second+=1
)
call :SUB !first! !second!
echo nr1 var: !nr1!
echo nr2 var: !nr2!
echo counter f: !first!
echo counter s: !second!
set "counter=active"
)
endlocal
exit /B
:SUB val_token1 val_token2
for /F "tokens=%~1,%~2" %%a in ("%output%") do (
if %~1 LSS %~2 (
set "nr1=%%a"
set "nr2=%%b"
) else if %~1 GTR %~2 (
set "nr1=%%b"
set "nr2=%%a"
) else (
set "nr1=%%a"
set "nr2=%%a"
)
)
exit /B
Since you are extracting tokens from the same string, I combined your two for /F loops into a single one. The if block in the for /F loop in the sub-routine :SUB is there just in case the second token number is not always greater than the first one. But if that can guaranteed, the for /F loop needs to contain only set "nr1=%%a" and set "nr2=%%b".

How to have multiple sums in a variable in batch

I am trying to create a matrix this one works below) but is slow as it has to loop every number. So i was wondering if you can have multiple %random% %% 2 without just a "(random number) %2" being printed.
#echo off
color 0a
:a
set /a mat=%random% %% 2
echo |set /p=%mat%
goto a
I found this to be much faster:
#echo off
if not "%1" == "max" start /MAX cmd /c %0 max & exit/b
color 0a
setlocal EnableDelayedExpansion
:a
set "addition="
for /L %%i in (1,1,256) do (
set /a mat=!random! %% 2
set addition=!addition!!mat!
)
echo |set /p=%addition%
goto a
EDIT: I now see that the answer #Stephan links to in the comments is very similar, so credit goes to him too.
EDIT #2:
You might like this one too, has all kind of characters so it looks a bit more "matrixy"
#echo off
if not "%1" == "max" start /MAX cmd /c %0 max & exit/b
color a
setlocal EnableDelayedExpansion
set "CHARS=abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789######$$$%%%%%%*** "
echo %CHARS%>x&for %%? in (x) do set /a strlength=%%~z? - 2&del x
:a
call :randomString addition 100 "%chars%" %strlength%
echo |set /p=%addition%
goto a
:randomString
set "length=%2"
set "CHARS=%~3"
if ["%CHARS%"]==[""] set "CHARS=abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789######$$$%%%%%%***"
set "strlength=%4"
if [%strlength%]==[] echo %CHARS%>x&for %%? in (x) do set /a strlength=%%~z? - 2&del x
set "line="
for /L %%a in (1 1 %length%) do (
set /a "randnr=!random!%%!strlength!"
for /l %%n in (!randnr! 1 !randnr!) do set "line=!line!!CHARS:~%%n,1!"
)
set %1=%line%
exit /b
For extra effect add 6 spaces to the CHARS variable. Note that I also added a line to start it in full-screen mode.
The method below is the fastest possible way to solve this problem:
#echo off
setlocal EnableDelayedExpansion
rem Prepare the output for one line
set "bin=0101010101"
set "line="
for /L %%i in (1,1,79) do (
set "line=!line!%%bin:~^!random:~-1^!,1%%"
)
for /L %%i in () do call echo %line%

Read second parameter of a function into an array

I wrote the following code that works exactly the way I want:
#echo off
setlocal enabledelayedexpansion
set /a i=0
set in="This is line 1", "This is line 2"
for %%h in (%in%) do (
set /a i+=1
set "val[!i!]=%%h"
)
set out=
for /l %%n in (1,1,!i!) do (
set out=!out! !val[%%n]! ^& vbcrlf ^& _)
echo !out:~1,-12!
Which takes the value of the %in% variable and reads each comma separated line into an element of an array and then does some string concatenation on it and spits out a new string. Now, when I try to turn it into a function, it fails because of how %2 is being parsed as an argument. I need %2 to be parsed as a single, comma separated string with a variable amount of values. This simple test doesn't work:
call :Test Title "This is line 1","This is line 2" "arg3"
exit /b
:Test arg1 arg2 arg3
set /a i=0
for %%h in (%2) do (
set /a i+=1
set "val[!i!]=%%h"
)
set out=
for /l %%n in (1,1,!i!) do (
set out=!out! !val[%%n]! ^& vbcrlf ^& _)
echo %1 !out:~1,-12! %3
exit /b
The only thing I can think of is to use %* and change the delimiter to something unique but I'd rather avoid that if possible.
1. Shift through the Middle parameters
This will leave %1 alone and shift though all the middle parameters stopping when there are no more and leaving the last param in %3.
#echo off
setlocal
call :Test One "Two","Three" Four
endlocal
exit /b 0
:Test <Title> <Lines...> <LastArg>
echo %2
set "Temp=%4"
if defined Temp shift /2 & goto Test
echo %1
echo %3
exit /b 0
Output
"Two"
"Three"
One
Four
2. Put the string in a variable and pass the variable name
#echo off
setlocal EnableDelayedExpansion
set "String="Two","Three""
call :Test One String Four
endlocal
exit /b 0
:Test <a> <b> <c>
echo %1
echo !%2!
echo %3
exit /b 0
Output
One
"Two","Three"
Four
These are the first two solutions that come to my mind.
Update
Here is the shift method applied to your code using an inner loop :__Test
#echo off
setlocal EnableDelayedExpansion
call :Test Title "This is line 1","This is line 2" "arg3"
endlocal
exit /b 0
:Test <arg1> <arg2[,...]> <arg3>
set "i=0"
:__Test
set /a "i+=1"
set "val[!i!]=%2"
set "tempvar=%4"
if defined tempvar shift /2 & goto __Test
set "out="
for /l %%n in (1,1,!i!) do (
set out=!out! !val[%%n]! ^& vbcrlf ^& _)
echo %1 !out:~1,-12! %3
exit /b 0
For a general solution, passing values by reference (store value in a variable and pass variable name) is the best option. This is the same as David Ruhmann's second option.
There is another way, but it requires more work by the caller. You can require that all quotes in the parameter value be doubled up, and then enclose the entire parameter in one more set of quotes. Within the function, replace all "" with " to get the desired value. I used to use this method until I learned about passing values by reference.
#echo off
setlocal enableDelayedExpansion
call :Test Title """This is line 1"",""This is line 2""" "arg3"
exit /b
:Test arg1 arg2 arg3
set "arg2=%~2"
set "arg2=%arg2:""="%"
echo arg1=%1
echo arg2=%arg2%
echo arg3=%3
UPDATE
Passing values by reference is the best option to pass complex values that contain token delimiters and quotes.
But the OP isn't really interested in the list of values as a single parameter, since a FOR loop is used to split them up. The FOR loop can run into trouble if any of the values contain * or ?.
I now see that for this particular case, it is better to move the list to the end such that all arguments from 3 on are part of the list. Then use SHIFT /3 and a GOTO loop to read in the list values. This is basically David Ruhmann's option 1.

Resources