Windows Batch Iterating CSV list and selecting from second list - list

I have a list of projects and a list of project types. I need to iterate over the list of project types and every time the selected project type equals a certain value I need to get the Ith entry from the project names. For example:
Project Names: Project1,Project2,Project3
Project Types: web,ssis,ssis
I need all the "ssis" projects so the resulting list should be "Project2,Project3".
I've used for /f a lot but I don't know how to get the "i" in the "for" iteration so I can select the related entry from the second list. I've searched on this and gone down into the weeds on "for" but can't seem to come up with an answer.
If there's a method that works better than "for" I'm game. If there's a way to use "for" on the first list and pull the related entries out of the second list that would be great too.
Any help is greatly appreciated.

Based off a now-deleted comment:
#ECHO OFF
SET projlist=Project1,Project2,Project3
SET projtypes=web,ssis,ssrs
SET /a ptr=1
SETLOCAL ENABLEDELAYEDEXPANSION
FOR %%a IN (%projtypes%) DO (
ECHO %%a
SET index=1
FOR %%b IN (%projlist%) DO (
IF !index!==!ptr! ECHO %%b
SET /A index+=1
)
SET /a ptr=ptr+1
ECHO ----------------
)
ENDLOCAL
Running this will give this result:
web
Project1
----------------
ssis
Project2
----------------
ssis
Project3
----------------
Of course, you'll need to modify it to fit your exact needs.

If I understand it correctly, you want to group all project names that belongs to the same project type and show the result as a list of groups. I taken the answer of this post, that do a match of the elements of two lists, and modified it in order to get the groups you want:
#echo off
setlocal EnableDelayedExpansion
set ProjectNames=Project1,Project2,Project3
set ProjectTypes=web,ssis,ssis
rem Group project names by type
set i=0
for %%A in (%ProjectTypes%) do (
set /A i+=1, j=0
for %%B in (%ProjectNames%) do (
set /A j+=1
if !i! equ !j! set NamesByType[%%A]=!NamesByType[%%A]!,%%B
)
)
rem Show result
for /F "tokens=2,3 delims=[]=" %%a in ('set NamesByType[') do (
set list=%%b
echo Type %%a: !list:~1!
)
Output:
Type ssis: Project2,Project3
Type web: Project1
You may also consult a particular element of NamesByType array, for example:
echo %NamesByType[ssis]%

Related

An IF statement that checks and executes a command per each set variables in a windows batch file

Updated explanation:
I have a main batch file that branches out with subroutine batch scripts depending on what arguments are used in its execution. So, I need help with grabbing the NAMES of some preset values (only the names, i.e. TRIMAPP1, TRIMAPP2, etc.) and using them as values for the mentioned subroutine scripts.
In part, what Stephan suggested works (the %%a value) to provide me the names, but I then need to be able to use each result concurrently.
SET TRIMAPP1=APP1
SET TRIMAPP2=APP2
SET TRIMAPP3=APP3
SET TRIMAPP4=APP4
SET TRIMAPP5=APP5
if [%1%]==[TRIMMER] GOTO ONE
if not [%1%]==[2] GOTO NEXTSECTION
:ONE
for /f "tokens=1,* delims==" %%a in ('set TRIMAPP') do echo var %%a
…then need to run the following with each result for %%a
SET BACKUP DIRECTORY=C:\bkp\”%%a”
SET LOG DIRECTORY=C:\log\”%%a”_Logs
CALL C:\TRIM\TRIMMER.exe.
I’m expecting…
SET BACKUP DIRECTORY=C:\bkp\TRIMAPP1
SET BACKUP DIRECTORY=C:\bkp\TRIMAPP2
SET BACKUP DIRECTORY=C:\bkp\TRIMAPP3
SET BACKUP DIRECTORY=C:\bkp\TRIMAPP4
SET LOG DIRECTORY=C:\log\TRIMAPP1_Logs
SET LOG DIRECTORY=C:\log\TRIMAPP2_Logs
SET LOG DIRECTORY=C:\log\TRIMAPP3_Logs
SET LOG DIRECTORY=C:\log\TRIMAPP4_Logs
I hope this helps to understand better. Sorry for the confusion, I appreciate your patience. Thanks!
Something that you may find helpful is the behavior of the SET command when there is no equals sign. From SET/?, we can see
SET command invoked with just a variable name, no equal sign or value will display the value of all variables whose prefix matches the name given to the set command.
Thus, running SET TRIMAPP will give you:
TRIMAPP1=APP1
TRIMAPP2=APP2
TRIMAPP3=APP3
TRIMAPP4=APP4
TRIMAPP5=APP5
The FOR command can be used to iterate over the output of a command. Using "TOKENS=1,* DELIMS==" will split the output before and after the first equals sign, thus:
FOR /F "TOKENS=1,* DELIMS==" %%A IN ('SET TRIMAPP') DO (
REM PERCENT-A CONTAINS THE NAME OF THE VARIABLE
REM PERCENT-B CONTAINS THE VALUE OF THE VARIABLE
ECHO.Variable %%A has the value %%B.
)
will print
Variable TRIMAPP1 has the value APP1.
Variable TRIMAPP2 has the value APP2.
Variable TRIMAPP3 has the value APP3.
Variable TRIMAPP4 has the value APP4.
Variable TRIMAPP5 has the value APP5.
From what I gather from your explanation, you seek to use a parameter to search a group of associated variables for a value that matches said parameter.
You can use the method mentioned already by #Stephen and rehashed as a partial answer to pipe the output to Findstr, then Conditionally execute additional commands if the string is found using &&. EXAMPLE:
For /f "tokens=1* Delims==" %%G in ('Set "TRIMMAPP"')Do (
Echo(%%H|%__AppDir__%findstr.exe /LIC:"Str" && (
Echo(%%G = %%H
)
)
For the sake of using the above method with different search strings, You can make it a function by using a label and calling the label with an argument for the search string.
By using 2 Arguments, You can increase the reusability to allow use with other variable / groups of variables
#Echo off
SET TRIMAPP1=APP1
SET TRIMAPP2=APP2
SET TRIMAPP3=APP3
SET TRIMAPP4=APP4
SET TRIMAPP5=APP5
For %%i in (2 4)Do Call :FindVal TRIMAPP %%i
Pause
Exit /B 0
:FindVal <Variable prefix> <Search Value>
For /f "tokens=1* Delims==" %%G in ('Set "%~1"')Do (
Echo("%%H"|%__AppDir__%findstr.exe /LIC:"%~2" > nul && (
Echo(%%G = %%H
)
)
Exit /B 0
Note, the findstr usage should be adjusted depending on whether you are searching for a match that includes the value, or a match that is the exact value.
for /F "tokens=1*delims==" %%p IN ('set TRIMAPP') DO (
echo %%%%.p=%%~p %%%%.q=%%~q ^<^<
SET BACKUP DIRECTORY=C:\bkp\%%p
SET LOG DIRECTORY=C:\log\%%pLogs
CALL C:\TRIM\TRIMMER.exe
)
Can't see where the value of the variable %%p which is in %%q fits in.
Note for the regulars: %%%%.p shows as %%.p because %%%%p is ambiguous. It could mean escaped-%escaped-% or escaped-%valueofmetavariable p :(

How to create a list in batch coding [duplicate]

I was playing with cmd.exe, but in its help I didn't find any info, how to define arrays.
I have found, how to define simple variables:
set a=10
echo %a%
But, I want to create arrays, linked list etc...
So, does it able in cmd.exe ( I mean: does in cmd.exe exist any array keywords? )
I want to realize some algorithms as:
bubble sort
quick sort
gnome sort
etc...
So, I also want to know, does Cmd.exe have references or instances, structs etc?
Cause its help not full in: /?
Could Cmd.exe be defined as full by Turing-Machine definition? ( Turing-Complete )
Ok. I'll try to be as clear as possible to not be misunderstood...
In Windows Batch files a variable name should begin with a letter and may include any valid character, where valid characters are: #$'()*+,-.?#[]_`{}~ besides letters and digits.
This means that from the cmd.exe point of view, SET NORMAL_NAME=123 is exactly the same as SET A#$'()*+,-.?#[\]_{}~=123 and also the same as SET VECTOR[1]=123; all three are normal variables. This way, it is up to you to write variable names in the form of array elements:
set elem[1]=First element
set elem[2]=Second one
set elem[3]=The third one
This way, echo %elem[2]% will show Second one.
If you want to use another variable as index, you must know that the replacement of variables enclosed in percent symbols by their values is parsed from left to right; this means that:
set i=2
echo %elem[%i%]%
doesn't give the desired result because it means: show the value of the elem[ variable, followed by i, followed by the value of the ] variable.
To solve this problem you must use Delayed Expansion, that is, insert setlocal EnableDelayedExpansion command at the beginning, enclose index variables in percent symbols, and enclose the array elements in exclamation marks:
setlocal EnableDelayedExpansion
set elem[1]=First element
set elem[2]=Second one
set elem[3]=The third one
set i=2
echo !elem[%i%]!
You may also use parameters of FOR commands as indexes: for /L %%i in (1,1,3) do echo !elem[%%i]!. You must use !index! to store values in array elements when the index is changed inside a FOR or IF: set elem[!index!]=New value. To get the value of an element when the index changes inside FOR/IF, enclose the element in double percent symbols and precede the command with call. For example, to move a range of array elements four places to the left:
for /L %%i in (%start%,1,%end%) do (
set /A j=%%i + 4
call set elem[%%i]=%%elem[!j!]%%
)
Another way to achieve the previous process is to use an additional FOR command to change the delayed expansion of the index by an equivalent replaceable parameter, and then use the delayed expansion for the array element. This method runs faster than previous CALL:
for /L %%i in (%start%,1,%end%) do (
set /A j=%%i + 4
for %%j in (!j!) do set elem[%%i]=!elem[%%j]!
)
This way, the Batch file behaves like it manages arrays. I think the important point here is not to discuss if Batch manages arrays or not, but the fact that you may manage arrays in Batch files in an equivalent way of other programming languages.
#echo off
setlocal EnableDelayedExpansion
rem Create vector with names of days
set i=0
for %%d in (Sunday Monday Tuesday Wednesday Thrusday Friday Saturday) do (
set /A i=i+1
set day[!i!]=%%d
)
rem Get current date and calculate DayOfWeek
for /F "tokens=1-3 delims=/" %%a in ("%date%") do (
set /A mm=10%%a %% 100, dd=10%%b %% 100, yy=%%c
)
if %mm% lss 3 set /A mm=mm+12, yy=yy-1
set /A a=yy/100, b=a/4, c=2-a+b, e=36525*(yy+4716)/100, f=306*(mm+1)/10, jdn=c+dd+e+f-1523, dow=jdn %% 7 + 1
echo Today is !day[%dow%]!, %date%
Note that index values are not limited to numbers, but they may be any string that contain valid characters; this point allows to define what in other programming languages are called associative arrays. At this answer there is a detailed explanation of the method used to solve a problem using an associative array. Note also that the space is a valid character in variable names, so you must pay attention to not insert spaces in variable names that may go unnoticed.
I elaborated on the reasons I have to use array notation in Batch files at this post.
In this post there is a Batch file that reads a text file and stores the indexes of the lines in a vector, then does a Buble Sort of vector elements based on line contents; the equivalent result is a sort over file contents.
In this post there is a basic Relational Data Base application in Batch based on indexes stored in files.
In this post there is a complete multiple linked-list application in Batch that assembles a large data structure taken from a subdirectory and displays it in the form of TREE command.
Windows shell scripting really isn't designed to work with arrays, let alone complex data structures. For the most part, everything's a string in the windows shell, but, there are some things you can do to "work with" arrays, like declaring n variables VAR_1, VAR_2, VAR_3... using a loop and filtering on the prefix VAR_, or creating a delimited string and then using the FOR construct that iterates over a delimited string.
Similarly, you can use the same basic idea to create a struct-like set of variables like ITEM_NAME, ITEM_DATA or w/e. I even found this link that talks about simulating an associative array in CMD.
It is all terribly hackish and inconvenient when it comes down to it. The command-line shell just wasn't designed for heavy programming. I agree with #MatteoItalia -- if you need serious scripting, use a real scripting language.
I made a bubble sort implementation in batch using pseudo-arrays a while ago.
Not sure why you'd use it (although I will admit to doing so in another batch file) as it gets pretty slow as the list size increases. It was more to set myself a little challenge.
Someone might find this useful.
:: Bubblesort
:: Horribly inefficient for large lists
:: Dave Johnson implementation 05/04/2013
#echo off
setlocal enabledelayedexpansion
:: Number of entries to populate and sort
set maxvalue=50
:: Fill a list of vars with Random numbers and print them
for /l %%a in (1,1,%maxvalue%) do (
set /a tosort%%a=!random!
)
:: echo them
set tosort
:: Commence bubble sort
Echo Sorting...
set /a maxvalue-=1
set iterations=0
for /l %%a in (%maxvalue%,-1,1) do ( REM Decrease by 1 the number of checks each time as the top value will always float to the end
set hasswapped=0
for /l %%b in (1,1,%%a) do (
set /a next=%%b+1
set next=tosort!next!
set next=!next!
call :grabvalues tosort%%b !next!
rem echo comparing tosort%%b = !tosortvalue! and !next! = !nextvalue!
if !nextvalue! LSS !tosortvalue! (
rem set /a num_of_swaps+=1
rem echo Swapping !num_of_swaps!
set !next!=!tosortvalue!
set tosort%%b=!nextvalue!
set /a hasswapped+=1
)
)
set /a iterations+=1
if !hasswapped!==0 goto sorted
)
goto:eof
:grabvalues
set tosortvalue=!%1!
set nextvalue=!%2!
goto:eof
:sorted
::nice one our kid
set tosortvalue=
echo Iterations required: %iterations%
set tosort
endlocal
Concerning this statement*:
I have found, how to define simple variables:
set a = 10
echo %a%
This is simply wrong! Variable a will remain empty (supposing it was empty initially) and echo %a% will return ECHO is on. A variable called aSPACE will actually be set to the value SPACE10.
So for the code to work, you must get rid of the SPACEs around the equal-to sign:
set a=10
echo %a%
To make the assignment safe against all characters, use the quoted syntax (supposing you have the command extensions enabled, which is the default for the Windows command prompt anyway):
set "a=1&0"
echo(%a%
For all the rest of your question I recommend to read Aacini's great and comprehensive answer.
*) This statement has meanwhile been edited out.
Seriously speaking: I never heard that batch has arrays, maybe you can emulate them with some strange trick, but I wouldn't call it a good idea.
References/instances/structs are stuff for a real language, cmd scripting is just a bunch of extensions that grew over the very primitive interpreter that was command.com, you can do some basic scripting, but anything more complicated than a bunch of calls to other commands is doomed to become ugly and incomprehensible.
The only "advanced" construct is the do-it-all weirdo for loop, which, mixed with the strange "rules" of variable substitution (%var%, %%var, !var!, are different stuff because of the idiotic parser), makes writing even trivial algorithms a collection of strange hacks (see e.g. here for an implementation of quicksort).
My tip is, if you want to do your scripting in a sane way, use a real scripting language, and leave batch for simple, quick hacks and for backwards compatibility.
The following program simulates vectors (arrays) operations in cmd. The subroutines presented in it were initially designed for some special cases like storing the program parameters in an array or looping through filenames in a "for" loop and storing them in an array. In these cases, in an enabled delayed expansion block, the "!" characters - if present in values of the parameters or in the "for" loop variable's value - would get interpreted. That's why, in these cases, the subroutines must be used inside a disabled delayed expansion block:
#echo off
rem The subroutines presented bellow implement vectors (arrays) operations in CMD
rem Definition of a vector <v>:
rem v_0 - variable that stores the number of elements of the vector;
rem v_1..v_n, where n=v_0 - variables that store the values of the vector elements.
rem :::MAIN START:::
setlocal disabledelayedexpansion
rem Getting all the parameters passed to the program in the vector 'params':
rem Delayed expansion is left disabled in order not to interpret "!" in the program parameters' values (%1, %2, ... );
rem If a program parameter is not quoted, special characters in it (like "^", "&", "|") get interpreted at program launch.
:loop1
set "param=%~1"
if defined param (
call :VectorAddElementNext params param
shift
goto :loop1
)
rem Printing the vector 'params':
call :VectorPrint params
pause&echo.
rem After the vector variables are set, delayed expansion can be enabled and "!" are not interpreted in the vector variables's values:
echo Printing the elements of the vector 'params':
setlocal enabledelayedexpansion
if defined params_0 (
for /l %%i in (1,1,!params_0!) do (
echo params_%%i="!params_%%i!"
)
)
endlocal
pause&echo.
rem Setting the vector 'filenames' with the list of filenames in the current directory:
rem Delayed expansion is left disabled in order not to interpret "!" in the %%i variable's value;
for %%i in (*) do (
set "current_filename=%%~i"
call :VectorAddElementNext filenames current_filename
)
rem Printing the vector 'filenames':
call :VectorPrint filenames
pause&echo.
rem After the vector variables are set, delayed expansion can be enabled and "!" are not interpreted in the vector variables's values:
echo Printing the elements of the vector 'filenames':
setlocal enabledelayedexpansion
if defined filenames_0 (
for /l %%i in (1,1,!filenames_0!) do (
echo filenames_%%i="!filenames_%%i!"
)
)
endlocal
pause&echo.
endlocal
pause
rem :::MAIN END:::
goto :eof
:VectorAddElementNext
rem Vector Add Element Next
rem adds the string contained in variable %2 in the next element position (vector length + 1) in vector %1
(
setlocal enabledelayedexpansion
set "elem_value=!%2!"
set /a vector_length=%1_0
if not defined %1_0 set /a vector_length=0
set /a vector_length+=1
set elem_name=%1_!vector_length!
)
(
endlocal
set "%elem_name%=%elem_value%"
set %1_0=%vector_length%
goto :eof
)
:VectorAddElementDVNext
rem Vector Add Element Direct Value Next
rem adds the string %2 in the next element position (vector length + 1) in vector %1
(
setlocal enabledelayedexpansion
set "elem_value=%~2"
set /a vector_length=%1_0
if not defined %1_0 set /a vector_length=0
set /a vector_length+=1
set elem_name=%1_!vector_length!
)
(
endlocal
set "%elem_name%=%elem_value%"
set %1_0=%vector_length%
goto :eof
)
:VectorAddElement
rem Vector Add Element
rem adds the string contained in the variable %3 in the position contained in %2 (variable or direct value) in the vector %1
(
setlocal enabledelayedexpansion
set "elem_value=!%3!"
set /a elem_position=%2
set /a vector_length=%1_0
if not defined %1_0 set /a vector_length=0
if !elem_position! geq !vector_length! (
set /a vector_length=elem_position
)
set elem_name=%1_!elem_position!
)
(
endlocal
set "%elem_name%=%elem_value%"
if not "%elem_position%"=="0" set %1_0=%vector_length%
goto :eof
)
:VectorAddElementDV
rem Vector Add Element Direct Value
rem adds the string %3 in the position contained in %2 (variable or direct value) in the vector %1
(
setlocal enabledelayedexpansion
set "elem_value=%~3"
set /a elem_position=%2
set /a vector_length=%1_0
if not defined %1_0 set /a vector_length=0
if !elem_position! geq !vector_length! (
set /a vector_length=elem_position
)
set elem_name=%1_!elem_position!
)
(
endlocal
set "%elem_name%=%elem_value%"
if not "%elem_position%"=="0" set %1_0=%vector_length%
goto :eof
)
:VectorPrint
rem Vector Print
rem Prints all the elements names and values of the vector %1 on sepparate lines
(
setlocal enabledelayedexpansion
set /a vector_length=%1_0
if !vector_length! == 0 (
echo Vector "%1" is empty!
) else (
echo Vector "%1":
for /l %%i in (1,1,!vector_length!) do (
echo [%%i]: "!%1_%%i!"
)
)
)
(
endlocal
goto :eof
)
:VectorDestroy
rem Vector Destroy
rem Empties all the elements values of the vector %1
(
setlocal enabledelayedexpansion
set /a vector_length=%1_0
)
(
endlocal
if not %vector_length% == 0 (
for /l %%i in (1,1,%vector_length%) do (
set "%1_%%i="
)
set "%1_0="
)
goto :eof
)
It is also possible to store the program parameters in an "array" or loop through the filenames in a directory using a "for" loop and store them in an "array" (without interpreting "!" in their values) without using the presented subroutines in the program above:
#echo off
setlocal disabledelayedexpansion
rem Getting all the parameters passed to the program in the array 'params':
rem Delayed expansion is left disabled in order not to interpret "!" in the program parameters' values (%1, %2, ... );
rem If a program parameter is not quoted, special characters in it (like "^", "&", "|") get interpreted at program launch.
set /a count=1
:loop1
set "param=%~1"
if defined param (
set "params_%count%=%param%"
set /a count+=1
shift
goto :loop1
)
set /a params_0=count-1
echo.
rem After the array variables are set, delayed expansion can be enabled and "!" are not interpreted in the array variables's values:
rem Printing the array 'params':
echo Printing the elements of the array 'params':
setlocal enabledelayedexpansion
if defined params_0 (
for /l %%i in (1,1,!params_0!) do (
echo params_%%i="!params_%%i!"
)
)
endlocal
pause&echo.
rem Setting the array 'filenames' with the list of filenames in the current directory:
rem Delayed expansion is left disabled in order not to interpret "!" in the %%i variable's value;
set /a count=0
for %%i in (*) do (
set "current_filename=%%~i"
set /a count+=1
call set "filenames_%%count%%=%%current_filename%%"
)
set /a filenames_0=count
rem After the array variables are set, delayed expansion can be enabled and "!" are not interpreted in the array variables's values:
rem Printing the array 'filenames':
echo Printing the elements of the array 'filenames':
setlocal enabledelayedexpansion
if defined filenames_0 (
for /l %%i in (1,1,!filenames_0!) do (
echo filenames_%%i="!filenames_%%i!"
)
)
endlocal
endlocal
pause
goto :eof
TLDR:
I hit upon the Idea of using a "For" loop and the "set" command to allow the parsing of variables, allowing me to create Pseudo Arrays, both ordered and linked-list style, and more importantly, Pseudo Objects akin to structures.
A typical batch Pseudo Array, and how to parse:
SET "_Arr.Names="Name 1" "Name 2" ... "Name N""
FOR %A IN (%_Arr.Names%) DO #( Echo.%~A )
REM Results:
REM Name 1
REM Name 2
REM ...
REM Name N
Below we make some Dumb Pseudo Arrays and a manual ordered Pseudo Array, plus create an Ordered Pseudo Array catching the output of a DIR Command.
We also take the Dumb Pseudo Arrays and convert them into Ordered arrays (removing the original Dumb Pseudo Array variables after).
We then update all of the ordered Arrays to contain more elements manually.
Finally we Dynamically report some of the values from the Array by doing a pre-defined For L Loop for values 7 to 9, and Generating a Random value to print the 4th example value of the array.
Note:
I create a variable to hold the method for adding members to make adding them simpler.
I point this out as it should make it easy to see how we make the minor jump from ordered arrays to Pseudo objects.
#(
SETLOCAL ENABLEDELAYEDEXPANSION
ECHO OFF
REM Manually Create a shortcut method to add more elements to a specific ordered array
SET "_Arr.Songs.Add=SET /A "_Arr.Songs.0+=1"&&CALL SET "_Arr.Songs.%%_Arr.Songs.0%%"
REM Define some 'dumb' Pseudo arrays
SET "_Arr.Names="Name 1" "Name 2" "Name 3" "Name 4" "Name 5" "Name 6" "Name 7" "Name 8""
SET "_Arr.States="AL" "AK" "AZ" "AR" "CA" "CO" "CT" "DE" "FL" "GA" "HI" "ID" "IL" "IN" "IA" "KS" "KY" "LA" "ME" "MD" "MA" "MI" "MN" "MS" "MO" "MT" "NE" "NV" "NH" "NJ" "NM" "NY" "NC" "ND" "OH" "OK" "OR" "PA" "RI" "SC" "SD" "TN" "TX" "UT" "VT" "VA" "WA" "WV" "WI" "WY""
)
REM Manually Create One Ordered Array
%_Arr.Songs.Add%=Hey Jude"
%_Arr.Songs.Add%=The Bartman"
%_Arr.Songs.Add%=Teenage Dirtbag"
%_Arr.Songs.Add%=Roundabout"
%_Arr.Songs.Add%=The Sound of Silence"
%_Arr.Songs.Add%=Jack and Diane"
%_Arr.Songs.Add%=One Angry Dwarf and 200 Solumn Faces"
REM Turn All Pre-Existing Normal Pseudo Arrays into Element Arrays
REM Since Ordered Arrays use Index 0, we can skip any manually created Ordered Arrays:
FOR /F "Tokens=2 Delims==." %%A IN ('SET _Arr. ^| FIND /V ".0=" ^| SORT') DO (
IF /I "%%~A" NEQ "!_TmpArrName!" (
SET "_TmpArrName=%%~A"
IF NOT DEFINED _Arr.!_TmpArrName!.Add (
REM Create a shortcut method to add more members to the array
SET "_Arr.!_TmpArrName!.Add=SET /A "_Arr.!_TmpArrName!.0+=1"&&CALL SET "_Arr.!_TmpArrName!.%%_Arr.!_TmpArrName!.0%%"
)
FOR %%a IN (!_Arr.%%~A!) DO (
CALL SET /A "_Arr.!_TmpArrName!.0+=1"
CALL SET "_Arr.!_TmpArrName!.%%_Arr.!_TmpArrName!.0%%=%%~a"
)
)
IF DEFINED _Arr.!_TmpArrName! (
REM Remove Unneeded Dumb Psuedo Array "_Arr.!_TmpArrName!"
SET "_Arr.!_TmpArrName!="
)
)
REM Create New Array of unknown Length from Command Output, and Store it as an Ordered Array
SET "_TmpArrName=WinDir"
FOR /F "Tokens=* Delims==." %%A IN ('Dir /B /A:D "C:\Windows"') DO (
IF NOT DEFINED _Arr.!_TmpArrName!.Add (
SET "_Arr.!_TmpArrName!.Add=SET /A "_Arr.!_TmpArrName!.0+=1"&&CALL SET "_Arr.!_TmpArrName!.%%_Arr.!_TmpArrName!.0%%"
)
CALL SET /A "_Arr.!_TmpArrName!.0+=1"
CALL SET "_Arr.!_TmpArrName!.%%_Arr.!_TmpArrName!.0%%=%%~A"
)
)
REM Manually Add additional Elements to the Ordered Arrays:
%_Arr.Names.Add%=Manual Name 1"
%_Arr.Names.Add%=Manual Name 2"
%_Arr.Names.Add%=Manual Name 3"
%_Arr.States.Add%=51st State"
%_Arr.States.Add%=52nd State"
%_Arr.States.Add%=53rd State"
%_Arr.Songs.Add%=Live and Let Die"
%_Arr.Songs.Add%=Baby Shark"
%_Arr.Songs.Add%=Safety Dance"
%_Arr.WinDir.Add%=Fake_Folder 1"
%_Arr.WinDir.Add%=Fake_Folder 2"
%_Arr.WinDir.Add%=Fake_Folder 3"
REM Test Output:
REM Use a For Loop to List Values 7 to 9 of each array and A Psuedo Rnadom 4th value
REM We are only interested in Ordered Arrays, so the .0 works nicely to locate those exclusively.
FOR /F "Tokens=2,4 Delims==." %%A IN ('SET _Arr. ^| FIND ".0=" ^| SORT') DO (
CALL :Get-Rnd %%~B
ECHO.
ECHO.%%~A 7 to 9, Plus !_Rnd#! - Psuedo Randomly Selected
FOR /L %%L IN (7,1,9) DO (
CALL Echo. * Element [%%L] of %%~A Pseudo Array = "%%_Arr.%%~A.%%L%%"
)
CALL Echo. * Random Element [!_Rnd#!] of %%~A Pseudo Array = "%%_Arr.%%~A.!_Rnd#!%%"
)
ENDLOCAL
GOTO :EOF
:Get-Rnd
SET /A "_RandMax=(32767 - ( ( ( 32767 %% %~1 ) + 1 ) %% %~1) )", "_Rnd#=!Random!"
IF /I !_Rnd#! GTR !_RandMax! ( GOTO :Get_Rnd# )
SET /A "_Rnd#%%=%~1"
GOTO :EOF
Example Results:
Results:
Names 7 to 9, Plus 5 - Psuedo Randomly Selected
* Element [7] of Names Pseudo Array = "Name 7"
* Element [8] of Names Pseudo Array = "Name 8"
* Element [9] of Names Pseudo Array = "Manual Name 1"
* Random Element [5] of Names Pseudo Array = "Name 5"
Songs 7 to 9, Plus 5 - Psuedo Randomly Selected
* Element [7] of Songs Pseudo Array = "One Angry Dwarf and 200 Solumn Faces"
* Element [8] of Songs Pseudo Array = "Live and Let Die"
* Element [9] of Songs Pseudo Array = "Baby Shark"
* Random Element [5] of Songs Pseudo Array = "The Sound of Silence"
States 7 to 9, Plus 9 - Psuedo Randomly Selected
* Element [7] of States Pseudo Array = "CT"
* Element [8] of States Pseudo Array = "DE"
* Element [9] of States Pseudo Array = "FL"
* Random Element [9] of States Pseudo Array = "FL"
WinDir 7 to 9, Plus 26 - Psuedo Randomly Selected
* Element [7] of WinDir Pseudo Array = "assembly"
* Element [8] of WinDir Pseudo Array = "AUInstallAgent"
* Element [9] of WinDir Pseudo Array = "Boot"
* Random Element [26] of WinDir Pseudo Array = "Fonts"
Initially I would do things similar to Aacini, a simple line of variables with an incremental counter, manually, or assigning them through a simple loop from a quick list of variables.
This was fine for small 2-D Arrays.
However I find it a pain for long arrays of data, especially when I need multi-value content.
To say nothing of when I need to match and populate content in those multi-dimensional arrays dynamically, where the simple usage there breaks down.
I found that it became hard when you ended up needing multiple arrays of information which you needed to update or add features to across the board.
As such an array is essentially a list of sub-strings you need to exports as variables, and adding or changing their ordering means changing your code.
Take for instance a scenario where you need to log into multiple FTP servers, delete files older than X days from certain paths.
Initially you might create simple arrays of substrings I'll define like this:
Site.##=[Array (String)] [Array (String)] #(
IP=[SubSting],
Username=[SubString],
Password[SubString])
Or as shown in in this example code.
(
SETOCAL
ECHO OFF
REM Manage Sites:
SET "Sites=13"
SET "MaxAge=28"
SET "Site.1="[IP]" "[User Name]" "[Password]" "[Path]""
SET "Site.2="[IP]" "[User Name]" "[Password]" "[Path]""
SET "Site.3="[IP]" "[User Name]" "[Password]" "[Path]""
REM ...
SET "Site.11="[IP]" "[User Name]" "[Password]" "[Path]""
SET "Site.12="[IP]" "[User Name]" "[Password]" "[Path]""
SET "Site.13="[IP]" "[User Name]" "[Password]" "[Path]""
)
FOR /L %%L IN (1,1,%Sites%) DO (
FOR /F "Tokens=*" %%A IN ('CALL ECHO %%Site.%%L%%') DO (
Echo. Pulled this example from a more complex example of my actual code, so the example variables may not need this loop, but it won't hurt to have if they don't need the extra expansion.
Call :Log
CALL :DeleteFTP %%~A
)
)
GOTO :EOF
:DeleteFTP
REM Simple ftp command for cygwin to delete the files found older than X days.
SET "FTPCMD="%~dp0lftp" %~1 -u %~2,%~3 -e "rm -rf %~4%MaxAge% "
FOR /F "Tokens=*" %%F IN ('"%FTPCMD% 2^>^&1"') DO #(
ECHO.%%~F
)
GOTO :EOF
Now, 13 sites, this isn't all that bad, I'm sure you're saying. right? You can just add one at the end and then put in the info and done.
Then you need to add the names of the sites in for reporting, so you add another term to each string at place 5 so you don't have to change your function..
::...
SET "Site.1="[IP]" "[User Name]" "[Password]" "[Path]" "[Site Name]""
::...
Then you realise you'll need to keep them in order by their site names (or IPs, but the names are easier for most people to remember and you need to be able to let other people have a look) so you change the order in all 13 spots, the call to expand the variables, and the function.
::...
SET "Site.1="[Site Name]" "[IP]" "[User Name]" "[Password]" "[Path]""
::...
FOR /F "Tokens=*" %%A IN ('CALL ECHO %%Site.%%L%%')
::...
SET "FTPCMD="%~dp0lftp" %~2 -u %~3,%~4 -e "rm -rf %~5%MaxAge% "
::...
Then it just keeps getting worse:
The number of directories you have to check, using different users, at the same site begins increasing.
You realise you need to have different retention times per site, and later, per directory.
You end up having 30, 40,50 of these and it's hard to remember which is which by looking at the end of a long string and copying them around, etc.
You stopped adding more paths, but sometime you have to remove old ones or it causes problems when they are gone, and if you forget to update the total number of site sin the list you might miss running the script on some.
when a directory is added or removed you have to add it/remove it fro each site making it harder to use the ordering, and easier to miss sites as they aren;t easy to ID.
Just, what a pain, and this isn't even when you need to have a dynamic set of objects, this is all manual.
So what can you do? Well, here's what I did:
I ended up resorting to implementing a sort of poor-mans Structure or Object-array (of strings) in my cmd scripts where the need fits.
IE the structure would be a "Site Object" which would have multiple properties, which might be objects with sub properties themselves. Since CMD is not actually Object oriented, its a bit of a kludge, just as arrays are.
Since the example I started with ended up being the first place I tried these you can see this intermediate amalgam step I'll define like this:
eg: Site.[ID].[Object Property]=[Value, or array of values]
Site
.ID=[int]
.Name=[string]
.Path=[String]
.MaxAge=[Int]
.Details=[Array (String)] #(
IP=[SubSting],
Username=[SubString],
Password[SubString])
To combat the issue with needing to re-order sets of Data on the fly I considered using a form of linked lists I toyed with, but since I wanted to easily add items to each grouping of sites while retaining order between sites I settled on a simple method.
Here is another code example of this step in usage:
#(
SETLOCAL ENABLEDELAYEDEXPANSION
ECHO OFF
SET "_SiteCount=0"
SET "_SiteID=0"
SET /A "_SiteID= !_SiteID! + 1"
SET "Site.!_SiteID!.MaxAge=Day5Ago"
SET "Site.!_SiteID!.Name=[SITE NAME HEADER FOR EMAIL]"
SET "Site.!_SiteID!.Detail="[IP]" "[UserName]" "[Password]" "[Path]""
REM ...
SET /A "_SiteID= !_SiteID! + 1"
SET "Site.!_SiteID!.MaxAge=Day15Ago"
SET "Site.!_SiteID!.Name=[SITE NAME HEADER FOR EMAIL]"
SET "Site.!_SiteID!.Detail="[IP]" "[UserName]" "[Password]" "[Path]""
)
CALL :Main
(
ENDLOCAL
Exit /b %eLvl%
)
:Main
REM In some forms of these the order isn't meaningful, but in others you need to follows the order and so we just count he number of site objects by counting one of their properties.
FOR /F %%A IN ('SET ^| FIND /I "Site." ^| FIND /I ".Name="') DO ( CALL SET /A "_SiteCount+=1" )
FOR /L %%L IN (1,1,34) DO (
CALL :PSGetDate_DaysAgo %%L
)
FOR /L %%L IN (1,1,%_SiteCount%) DO (
SET "Site.%%L.Create=NONE"
)
FOR /L %%L IN (1,1,%_SiteCount%) DO (
FOR /F "Tokens=*" %%A IN ('CALL ECHO ""%%Site.%%L.Name%%" %%Site.%%L.Detail%% "Site.%%L" "%%%%Site.%%L.MaxAge%%%%""') DO (
CALL ECHO CALL :DeleteFTP %%~A
CALL :DeleteFTP %%~A
)
)
CALL :SendMail "%EMLog%" "%_EMSubject%"
GOTO :EOF
:DeleteFTP
REM ECHO.IF "%~7" EQU "%skip%" (
IF "%~7" EQU "%skip%" (
GOTO :EOF
)
SET "FTPCMD="%~dp0lftp" %~2 -u %~3,%~4 -e "rm -rf %~5%~7 "
SET "FTPCMD=%FTPCMD%; bye""
FOR /F "Tokens=*" %%F IN ('"%FTPCMD% 2^>^&1"') DO #(
ECHO."%%F"
ECHO."%%~F"
REM CALL :Output "%Temp%\%~2_%~7.log" "%%F"
%OP% "%Temp%\%~2_%~7.log"
SET "FTPOut=%%~F"
)
GOTO :EOF
As you can probably see, these structures work very well where you have sets of forking hierarchical data that you need to apply manually and show data in a specific sequential order.
Although, to be sure today I usually make the base of the structures the name of the script, as I find this is more useful, and may or may not use ordered arrays depending on need.
SET "_GUID=^%Time^%_^%Random:~-1^%^%Random:~-1^%^%Random:~-1^%^%Random:~-1^%^%Random:~-1^%^%Random:~-1^%^%Random:~-1^%^%Random:~-1^%^%Random:~-1^%"
eg: %~n0.[ObjectName].[Object Property].[Object Sub Property]=[Value, or array of values]
[Script Name]
.[Object Name](May Hold Count of Names)=[int]
.Name=[string]
.Paths(May Hold Count of IDs)=[INT]
.GUID=%_GUID%
.Path=String
.MaxAge=[Int]
.Details=[Array (String)] #(
IP=[SubSting],
Username=[SubString],
Password[SubString])
But what about where you might have to collect large sets of dynamically generated data, and group it into pre-made categories and then mix that up to report it.
Well here again these can be useful too, and you can build them on the fly in your code adding more properties as needed.
In a similar script to the FTP delete, we need to check the sizes of multiple directories, I am going to dumb tis one down quite a bit and look at just one check:
#(
SETLOCAL ENABLEDELAYEDEXPANSION
ECHO OFF
SET /A "_SiteID= !_SiteID! + 1"
SET "SiteName=SiteA"
SET "%~n0.!SiteName!=%%_SiteID%%
SET "%~n0.!SiteName!.SiteID=!_SiteID!
SET "%~n0.!SiteName!.Paths="PathA" "PathB" "PathC" "PathD" "PathE""
)
CALL :CheckFTP [FTP Login variables from source object including Site ID]
:CheckFTP
REM Not necessary to assign Variables, doing this for exposition only:
CALL SET "TempSiteName=%~6"
CALL SET "TempPaths=%%%~n0.%~1.Paths%%"
REM Clear the site Temp KB variables
FOR \F "Tokens=2* Delims== " %%H IN (%TempPaths% "Total" "Temp") DO (
CALL SET /A "%%%~n0.%~1.Paths.%%~H.KB=0"
)
FOR %%J IN (%TempPaths%) DO (
FOR /F "Tokens=1-2" %%F IN ('[FTP Command using source object options]') DO #(
CALL :SumSite "%~6" "%%~F" "%%~G"
FOR /F "Tokens=1,2,* delims=/" %%f IN ("%%~G") DO (
CALL :ConvertFolder "%~6" "%%~F" "%%~g" "%%~h" "%~6_%%~g_%%~h"
)
)
)
FOR /F "Tokens=3,4,7 Delims==_." %%g IN ('SET ^| FIND /I "%~6_" ^| FIND /I ".KB" ^| FIND /I /V "_."') DO (
CALL :WriteFolder "%%g/%%~h" "%TmpFile%" "%~6_%%~g_%%~h"
REM echo.CALL :WriteFolder "%%g/%%~h" "%TmpFile%" "%~6_%%~g_%%~h"
)
CALL :ConvertSite "%~1"
CALL :WriteTotalFolder "%~7" "%TmpFile%" "%~6"
CALL :SendMail "%TmpFile%" "Backup_%~1"
GOTO :EOF
:SumSite
CALL SET "TSumPaths=%%%~n0.%~1.Paths%% "Total""
FOR %%H IN (%TSumPaths%) DO (
CALL SET /A "%~n0.%~1.Paths.%%~H.KB=%%%~n0.%~1.Paths.%%~H.KB%%+%~2"
)
:SumSite
CALL SET "TSumPaths=%%%~n0.%~1.Paths%% "Total""
FOR %%H IN (%TSumPaths%) DO (
CALL SET /A "%~n0.%~1.Paths.%%~H.KB=%%%~n0.%~1.Paths.%%~H.KB%%+%~2"
)
GOTO :EOF
:ConvertFolder
REM Convert's Folder values to MB and GB
SET /A "%~1.Temp.KB=%~2"
CALL SET /A "%~1.Temp.MB=%%%~1.Temp.KB%%/1024"
CALL SET /A "%~1.Temp.GB=(%%%~1.Temp.KB%%/1024)/1024"
CALL SET /A "%~5.Temp.KB=%%%~5.Temp.KB%%+%~2"
CALL SET /A "%~5.Temp.MB=%%%~5.Temp.KB%%/1024"
CALL SET /A "%~5.Temp.GB=(%%%~5.Temp.KB%%/1024)/1024"
GOTO :EOF
:WriteFolder
CALL :PickGMKBytes "%~1" "%~2" "G" "M" "K" "%%%~3.Temp.GB%%" "%%%~3.Temp.MB%%" "%%%~3.Temp.KB%%"
GOTO :EOF
:PickGMKBytes
IF /I "%~6" NEQ "" (
IF /I "%~6"=="0" (
CALL :PickGMKBytes "%~1" "%~2" "%~4" "%~5" "%~6" "%~7" "%~8"
) ELSE (
CALL :Output "%~2" "%~6%~3 %~1"
)
) ELSE (
CALL :Output "%~2" "0B %~1"
)
GOTO :EOF
:ConvertSite
CALL SET "TempPaths=%%%~n0.%~1.Paths%%"
FOR %%V IN (%TempPaths% "Total") DO (
CALL SET /A "%~1.%%~V.MB=%%%~1.%%~V.KB%%/1024"
CALL SET /A "%~1.%%~V.GB=(%%%~1.%%~V.KB%%/1024)/1024"
)
GOTO :EOF
To be fair, this script example may not be very explicit in showing what is happening, and I had to make changes on the fly to fix a new object style, but essentially:
It creates connection objects, and then dynamically extends them to include sub folders, and maintain running totals for each subfolder and site in KB, MB, and GB, and pics which of the values to report after summing up all of the directories for a given folder etc dynamically.
While i had to edit it a bit because this is also an earlier version of these as well, I thought it was one of the instances where it might best show the benefits. If I find a better example in one of my other scripts I might update it there as well.
On the subject of "Turing Completeness in Batch programming"
Yes, Batch is Turing complete according to best of my knowledge (and if you ignore the "unlimited" features like unlimited memory and computational time; so one could argue that Batch is only "theoretically Turing equivalent").
There are all basic boolean and arithmetic operators as well as loops (for) and branching (if). Also there is a goto functionality which allows modeling loops (while/do while/for) and sub routines. Nesting of blocks is possible. Variables can be named, stored, deleted/cleared, displayed/written to file. A halt condition can be reached with exit (or goto eof).
As a sidenote: It is possible to write a batch file from inside a batch program, write it to disk and run it (allows self modification/customization/sub routines/state saving and restoring).
However there is no unlimited memory storage. Only 32-Bit arithmetics can be used in computations. And obviously the computer running the batch file also has hardware and physical limits (only finite time, speed or space).
It should be noted that all "higher level" concepts you mentioned are not part of the "batch programming language". There is no concept of classes, objects, records/structs, arrays, linked lists, stacks, queues, etc integrated. Nor are there any default algorithms like sorting, etc. provided (except maybe if sort or findStr, more etc. with pipes are taken into consideration). Randomizing is also very basic with the %RANDOM% variable.
If you need those concepts, you need to model them with the given basic language elements I mentioned above on your own (or use some library/third-party-batchfiles for that matter).
Of course it is possible to call not only batch files but any supplemental program on the computer and return to the batch execution afterwards (communicating via file, standard I/O streams or exit/errorlevel codes). Those programs could have been written in higher level languages that provides those sorts of things in a more convenient way.
From my point of view Bash (Linux) and Powershell (Windows/Linux) are far more advanced in those fields.
All purpose Array handling script
#ECHO OFF
Set "UseErr=Echo/&Echo/Usage Error - Ensure command extensions and Delayed Expansion are enabled with: &Echo/Setlocal EnableExtensions EnableDelayedExpansion&Echo/ or from the command line:&Echo/CMD /V:On /K&Exit /B 1"
If Not "!Comspec!"=="%Comspec%" (%UseErr%)
(Set "GRPNm="&Set "TAB= "&Set "S_Offset="&Set "mode="&Set "#STDOut="&Set "nGRPNm="&Set "#Order="&Set "#Help="&Set "Inset="&Set "Usage=Echo/###&Exit /B 1") > Nul 2> Nul
(Set "SwParam="&Set "SwFParam="&Set "#ORP#=0"&Set "#FP#=0"&Set "Inset="&Set "#STDOut=0"&Set "GRPNm="&Set "!GRPNm!="&Set "SubEl="&Set "FlNm=%~n0"& Set "Mode="&Set "FindV=") > Nul 2> Nul
If "%~1"=="" (
Echo/&Echo/Modes:&Echo/ [Def]!TAB!!TAB!!TAB!Define, modify or clear an array.
Echo/ [Def]!TAB!!TAB!!TAB!Switches:!TAB![/A:Groupname] [/F:Filepath.ext] [/D] [/O:Index#Arg] [/E:Element Sub value] [[element0] ~ [element#]]
Echo/ [Sort-int]!TAB!!TAB!Sorts array by lowest or highest value using /L or /H switches
Echo/ [Sort-int]!TAB!!TAB!Switches:!TAB![/A:Groupname] [/N:New Groupname] [/L^|/H] [/D]
Echo/ [Sort-str]!TAB!!TAB!Sorts an array or text files string values using alphanumerical order of sort: [0-9][a-z]
Echo/ [Sort-str]!TAB!!TAB!Switches:!TAB![/A:Groupname] [/F:Filepath.ext] [/D]
Echo/ [Find] !TAB!!TAB!Searches an array for the string value supplied.
Echo/ [Find] [searchstring]!TAB!Switches: [/A:Groupname]&Echo/
%Usage:###=/M:Mode required&Echo/[Def][Sort-int^|str][Find-Value]%
) Else Call :GetArgs %*
If Errorlevel 1 Exit /B 1
If "!Mode!"=="" (%Usage:###=/M:Mode required&Echo/[Def][Sort-int^|str][Find-Value]%)
Call :!Mode! %* 2> Nul || (%Usage:###=Invalid Mode or switch error for /M:!Mode!&Echo/[Def][Sort-int^|str][Find-Value]%)
Exit /B 0
:str
Set "Usage=Echo/###&Echo/Call !FlNm! ["/F:filepath.ext" ^| "/A:Array Group Name"] & Exit /B 1"
Set "#!GRPNm!=0"
If "!#FP#!"=="1" (
(For /F "UseBackQ Delims=" %%G in (`Type "!FilePath!" ^| Sort`)Do (
For %%x in ("!GRPNm![!#%GRPNm%!]") Do (
Setlocal DisableDelayedExpansion
Endlocal & Set "%%~x=%%~G"
If "!#STDOut!"=="1" Echo/%%~x=%%~G
)
Set /A "#!GRPNm!+=1"
)) 2> Nul || (%Usage:###:=Echo/Invalid Filepath:"!FilePath!"%)
Exit /B 0
)
If Not "!#FP#!"=="1" (For /F "Tokens=1,2 Delims==" %%G in ('Set !GRPNm![')Do Echo/%%H)>"%TEMP%\__Sort.txt"
(For /F "UseBackQ Delims=" %%G in (`Type "%TEMP%\__Sort.txt" ^| Sort`)Do (
For %%x in ("!GRPNm![!#%GRPNm%!]") Do (
Setlocal DisableDelayedExpansion
Endlocal & Set "%%~x=%%~G"
If "!#STDOut!"=="1" Echo/%%~x=%%~G
)
Set /A "#!GRPNm!+=1"
)
)
Del /Q "%TEMP%\__Sort.txt"
Exit /B 0
:Find
Set "Usage=Echo/###&Echo/Call !FlNm! [/M:Find-Searchstring] [/A:Group Name]&Exit /B 1"
If "!FindV!"=="" (%Usage:###=/M:Find-Value Required%)
(For /F "Tokens=1,2 Delims==" %%i in ('Set !GRPNm![') Do Echo/"%%j"|"%__AppDir__%findstr.exe"/LIC:"!FindV!" > Nul 2> Nul && (Echo/!FindV! found:&Echo/%%~i=%%~j))
Exit /B 0
:Int
Set "Usage=Echo/###&Echo/Call !FlNm! [/M:Sort-Int] [/A:Group Name] [/N:New Group Name] [Sort-Int] [/H^|/L]&Echo/Call %~n0 [/M:Sort-Int] [/A:Groupname] [Sort-Int] [/H^|/L]&Exit /B 1"
If "!#Help!"=="1" (%Usage:###=/M:Sort-Int Usage:%)
If "!nGRPNm!"=="" Set "nGRPNm=!GRPNm!"
If Not "%#Order%"=="" (Call :Sort%#Order% !nGRPNm! #!nGRPNm! !Inset!) Else (%Usage:###=Sort Order Required /H or /L%)
Exit /B 0
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: Subroutines for Population of Arrays with numeric values in sorted order.
:sortL <Element_VarName> <Element_Index_VarName> <Variable Names containing the values to be Sorted and Populated to the Array>
Set "%2=0"
FOR %%P In (%*) DO If Not "%%P"=="%1" If Not "%%P"=="%2" If Not "%%P"=="" (
Set "%1[!%2!]=!%%P!"
Set /A "%2+=1"
)
For /L %%a In (1,1,!%2!)Do (
Set /A "S_Offset=%%a - 1"
For /L %%b IN (0,1,%%a)Do (
If not %%b==%%a For %%c in (!S_Offset!)Do (
IF !%1[%%c]! LEQ !%1[%%b]! (
Set "tmpV=!%1[%%c]!"
Set "%1[%%c]=!%1[%%b]!"
Set "%1[%%b]=!tmpV!"
))))
Set /A %2-=1
If "!#STDOut!"=="1" For /L %%G in (0 1 !%2!)Do Echo/%1[%%G]=!%1[%%G]!
Exit /B 0
:sortH <Element_VarName> <Element_Index_VarName> <Variable Names containing the values to be Sorted and Populated to the Array>
Set "%2=0"
FOR %%P In (%*) DO If Not "%%~P"=="%~1" If Not "%%~P"=="%2" If Not "%%P"=="" (
Set "%1[!%2!]=!%%~P!"
Set /A "%2+=1"
)
For /L %%a In (1,1,!%2!)Do (
Set /A "S_Offset=%%a - 1"
For /L %%b IN (0,1,%%a)Do (
If not %%b==%%a For %%c in (!S_Offset!)Do (
If Not !%1[%%c]! LSS !%1[%%b]! (
Set "tmpV=!%1[%%c]!"
Set "%1[%%c]=!%1[%%b]!"
Set "%1[%%b]=!tmpV!"
))))
Set /A %2-=1
If "!#STDOut!"=="1" For /L %%G in (0 1 !%2!)Do Echo/%1[%%G]=!%1[%%G]!
Exit /B 0
:Def
Set "Usage=Echo/###&Echo/Call !FlNm! [/M:Def] [/A:Groupname] ["element0" ~ "element#"] [/F:Filepath.ext] [/E:"Element sub value"]&Echo/ - Assign each line in the given filepath plus element parameters to the Array&Echo/Call %~n0 [/M:Def] [/A:Groupname] REM : Clears the Array for the given Group Name&Echo/Call %~n0 [/M:Def] [/A:Groupname] [element] [element] [/O:Index#Arg] REM : Overides Elements from the index supplied&Exit /B 0"
If "!#ORP#!"=="1" Echo/!SwParam!|"%__AppDir__%findstr.exe" /RX [0-9]* > Nul 2> Nul
If not "!SwParam!"=="" If Errorlevel 1 (%Usage:###=O:!SwParam! #Arg invalid. Only Integers accepted.%)
If "!GRPNm!"=="" (%Usage:###=/A:Groupname Required%)
If "!#ORP#!"=="1" Set "#!GRPNm!=0"
If "!#%GRPNm%!"=="" Set "#!GRPNm!=0"
If "%#FP#%"=="1" (
If exist "!FilePath!" (
For /F "Delims=" %%G in (!FilePath!)Do If Not "%%~G"=="" (
For %%x in ("!GRPNm![!#%GRPNm%!]")Do (
Setlocal DisableDelayedExpansion
If "%#STDOut%"=="1" Echo/%%~x=%%~G
Endlocal & Set "%%~x=%%G"
)
Set /A "#!GRPNm!+=1" > Nul
)
) Else (%Usage:###=/F:!FilePath! Invalid path%)
)
If not "!Inset!"=="" (
For %%G in (!Inset!)Do (
For %%x in ("%GRPNm%[!#%GRPNm%!]")Do (
Setlocal DisableDelayedExpansion
If "%#STDOut%"=="1" Echo/%%~x=%%~G
Endlocal & Set "%%~x=%%~G"
)
If Not "!SubEL!"=="" Set "%%~G=!SubEl!"
Set /A "#!GRPNm!+=1" > Nul
)
) Else (
If Not "%#FP#%"=="1" (
For /F "Tokens=1,2 Delims==" %%I in ('Set %GRPNm%')Do Set "%%~I=" > Nul 2> Nul
Set "#!GRPNm!=" > Nul 2> Nul
)
)
Exit /B 0
:GetArgs
If Not "!#Help!"=="1" If "%~1" == "" (
If /I "!Mode!"=="int" If "!GRPNm!"=="" (Echo/Call %~n0 [/M:Sort-int] [/A:GroupName] [/H^|/L] [/D]&%Usage:###=/A:Groupname Required%)Else If /I "!Mode!"=="int" (For /F "Tokens=1,2 Delims==" %%G in ('Set !GRPNm![')Do Set "Inset=!Inset! %%G") > Nul 2> Nul || (%Usage:###=Usage Error - /A:!GRPNm! is not defined%)
If /I "!Mode!"=="str" If "!GRPNm!"=="" (Echo/Call %~n0 [/M:Sort-str] [/A:GroupName] [/N:New Groupname] [/F:Filepath.ext] [/D]&%Usage:###=/A:Groupname Required%)Else If /I "!Mode!"=="str" (For /F "Tokens=1,2 Delims==" %%G in ('Set !GRPNm![')Do Set "Inset=!Inset! %%G") > Nul 2> Nul || (%Usage:###=Usage Error - /A:!GRPNm! is not defined%)
Exit /B 0
) Else If "%~1" == "" Exit /B 0
Set "Param=%~1"
Echo/"!Param!"|"%__AppDir__%findstr.exe" /LIC:"Find-" > Nul 2> Nul && ((Set "FindV=!Param:/M:Find-=!"&Set "Mode=Find")&Shift&Goto :GetArgs)
Echo/"!Param!"|"%__AppDir__%findstr.exe" /LIC:"/M:" > Nul 2> Nul && (
Set "MODE=!Param:*/M:=!"& Echo/"!Mode!"|"%__AppDir__%findstr.exe" /LIC:"Sort-" > Nul 2> Nul && (Set "Mode=!Mode:*Sort-=!")
If "!Param:*/M:=!"=="" (
Echo/&Echo/Modes:&Echo/ [Def]!TAB!!TAB!Define, modify or clear an array.
Echo/ [Sort-int]!TAB!Sorts array by lowest or highest value using /L or /H switches
Echo/ [Sort-str]!TAB!Sorts an array or text files string values using alphanumerical order of sort: [0-9][a-z]
Echo/ [Find:Value]!TAB!Searches an array for the string value supplied.&Echo/
%Usage:###=/M:Mode required&Echo/[Def][Sort-int^|str][Find-Value]%
)
Shift&Goto :GetArgs
)
Echo/"%~1"|"%__AppDir__%findstr.exe" /LIC:"/H" > Nul 2> Nul && (Set "#Order=H"&Shift&Goto :GetArgs)
Echo/"%~1"|"%__AppDir__%findstr.exe" /LIC:"/L" > Nul 2> Nul && (Set "#Order=L"&Shift&Goto :GetArgs)
Echo/"%~1"|"%__AppDir__%findstr.exe" /LIC:"/D" > Nul 2> Nul && (Set "#STDOut=1"&Shift&Goto :GetArgs)
Echo/"%~1"|"%__AppDir__%findstr.exe" /LIC:"/F:" > Nul 2> Nul && ((If Not "!Param:/F:=!"=="" (Set "#FP#=1"&Set "FilePath=!Param:/F:=!")Else %Usage:###=/F:Filepath.ext not Supplied%)&Shift&Goto :GetArgs)
Echo/"%~1"|"%__AppDir__%findstr.exe" /LIC:"/N:" > Nul 2> Nul && (Set "nGRPNm=!Param:*/N:=!"&(If "!Param:*/N:=!"=="" %Usage:###=/N:New Group Name required%)&Shift&Goto :GetArgs)
Echo/"%~1"|"%__AppDir__%findstr.exe" /LIC:"/A:" > Nul 2> Nul && (Set "GRPNm=!Param:*/A:=!"&(If "!Param:*/A:=!"=="" %Usage:###=/A:Group Name required%)&Shift&Goto :GetArgs)
Echo/"%~1"|"%__AppDir__%findstr.exe" /LIC:"/O:" > Nul 2> Nul && (Set "SwParam=!Param:*/O:=!"&(If Not "!Param:/O:=!"=="" (Set "#ORP#=1")Else %Usage:###=/O:#Arg not Supplied%)&Shift&Goto :GetArgs)
Echo/"%~1"|"%__AppDir__%findstr.exe" /LIC:"/E:" > Nul 2> Nul && (Set "SubEl=!Param:*/E:=!"&(If "!Param:/S:=!"=="" %Usage:###=/E:Sub Element not Supplied%)&Shift&Goto :GetArgs)
Set Inset=!Inset! %1
Shift&Goto :GetArgs
Modes:
[Def] Define, modify or clear an array.
[Def] Switches: [/A:Groupname] [/F:Filepath.ext] [/D] [/O:Index#Arg] [/E:Element Sub value] "element0" ~ "element#"
[Sort-int] Sorts array by lowest or highest value using /L or /H switches
[Sort-int] Switches:
[/A:Groupname] [/N:New Groupname] [/L|/H] [/D]
[Sort-str]
Sorts an array or text files string values using alphanumerical order
of sort: [0-9][a-z]
[Sort-str] Switches:
[/A:Groupname] [/F:Filepath.ext] [/D]
[Find-searchstring]
Searches an array for the string value supplied.
[Find-searchstring] Switches: [/A:Groupname]
One approach I've used before is using files as arrays and folders as dictionaries of arrays.
Now hear me out - it might stupid at first to you, but it has some merit to it.
The idea is that a file can be treated as an array, and even support native, easy-to-use array iteration with the FOR command.
array.txt
these
are
items
in
an
array
For a 2d array you can use a folder of files like the one above. (with names like 0.txt through 100.txt). Keep in mind that you might need to have a separate file to index these as a directory of arrays isn't necessarily sorted the way you'd expect in a for loop, and is really more like a hash map where it's string -> [string].
Alternatively, I'm sure it wouldn't be too hard to parse csv (keep in mind commas and tabs inside of string-values! ;) )
For a mixed array (where some items are other arrays, and some are strings) you could have a file formatted something like this:
complex-array.txt
"value
"1
"2
\path.txt
\path2.txt
and a folder like this:
complex-array\path.txt
complex-array\path2.txt
where if a line begins with one character it's a value, and another, it's a path (possibly relative to this file). Of course, this could be recursive.
There's one big gotcha though. The script leaves behind files that (probably) need to be cleaned up before each run. (I say before because it's not safe to assume that the computer won't be unplugged while this script is running).
I'm unsure about the performance downsides to this, and batch is pretty slow anyway so perhaps it won't matter. (I'm fairly certain that the variable-name mangling tactic is faster as the values would remain in memory longer)
#echo off
set array=
setlocal ENABLEEXTENSIONS ENABLEDELAYEDEXPANSION
set nl=^&echo(
set array=auto blue ^!nl!^
bycicle green ^!nl!^
buggy red
echo convert the String in indexed arrays
set /a index=0
for /F "tokens=1,2,3*" %%a in ( 'echo(!array!' ) do (
echo(vehicle[!index!]=%%a color[!index!]=%%b
set vehicle[!index!]=%%a
set color[!index!]=%%b
set /a index=!index!+1
)
echo use the arrays
echo(%vehicle[1]% %color[1]%
echo oder
set index=1
echo(!vehicle[%index%]! !color[%index%]!

Batch File If Error

Ok so I am working on a project and I am doing this and I get to the if statement in confirmOne and it gives me "( was not expected at this time." Please help!
Many of the stray "You got to here!" messages are from me trying to debug it. I really need this soon. Please help. I also tried deleting parts and it still doesn't seem to work. If you see any other errors please tell me as I need all the help I can get. Thank you!
:grabInput
echo Please enter the username of the user you wish to access.
REM - } End Echoing Information/Main Menu | Grab Input {
set /p result=
goto correctName
REM - } End Grab Input | Process Input {
:correctName
set /p input=%result%
goto confirmOne
:confirmOne
echo Got to confirmOne
pause
if %input%==[] (
pause
cls
echo Oops! Looks like you didn't enter anything! Try Agian!
echo.
echo ................................................................................
echo.
goto grabInput
) ELSE (
goto confirmTwo
)
:confirmTwo
echo Got to ConfirmTwo
pause
if %input%==~help (
goto helpMenu
) ELSE (
goto confirmThree
)
:confirmThree
echo Got to ConfirmThree
if %input%==~info (
goto infoMenu
) ELSE (
goto swapDrive
)
Well, if you didn't enter anything for %input%, then your if statement would look like if ==[] (.
Your if statement should look like if [%input%] == [] (
I also see a lot of unnecessary code, you should take a look over your script.
Batch ALWAYS works on strings.
with the statement if %input%==[], when %input% is set to [nothing] (which is what you're trying the detect), batch substitutes [nothing] fo %input% and gets
IF ==[] (
and is confused because '(' is not a comparison operator.
[] is not some magic mantra. It's an old method of detecting the presence of parameters such that [%1] would equal [] if the parameter was absent. It doesn't work when the variable contains spaces or some other characters.
if "%var%"=="" is better
if not defined var is better still
Note that
set /p var=
will NOT set var to [nothing] in you simply press enter, It will leave var unchanged.
Hence this
set var=something
set /p var=
will leave var set to something. You should code this as
set "var="
set /p var="Some prompt "
if not defined var echo VAR is not defined
The quotes around var= ensures that var is NOT set to [some spaces] if there are trailing spaces on the line.
Other than that, the sequence
goto somelabel
:somelabel
(REM lines are irrelevant) is superfluous.
equally, in
if somecondition (goto somewhere) else (goto somewhereelse)
:somewhereelse
the else condition is superfluous
Batch only notices :label as a DESTINATION for a GOTO or a CALL. and will otherwise simply charge straight through any :label it finds as though they were remarks statements.

super simple mistake, batch if statement

My short batch file keeps failing. what is wrong with my if?
:user
set /p usercommand = "Input: "
if %usercommand% equ "done"
echo got here!
goto end
else
echo not done
goto user
First - with set /p usercommand = you set variable named usercommand<space>. Remove space after intended name (so it becames set /p usercommand=).
That said, this you will run you into another error as your if else syntax is incorrect. It must be:
if "%usercommand%" equ "done" (
your commands here
) else (
your commands here
)
Please note I quoted %usercommand%. Without that your comparison will never be true (unless of course you require your input to be explicitly quoted)

How can I create a list of files to pass to an executable using a DOS batch file?

I want to get a (semi-colon separated) list of all .dll and .exe in a directory as a variable to pass as an argument to an executable. I've tried a few things but it never quite works as expected. I've got part way there - the following prints out a list of the files I'm after:
CLS
#ECHO OFF
SET INPUT_FILES=
for /f %%x in ('dir /b E:\Builds\MyProgram\Release\*.exe E:\Builds\MyProgram\Release\*.dll') do ECHO %%x
...but the next step isn't quite right and I'm struggling to work out what I've got wrong:
CLS
#ECHO OFF
SET INPUT_FILES=
for /f %%x in ('dir /b E:\Builds\MyProgram\Release\*.exe E:\Builds\MyProgram\Release\*.dll') do IF DEFINED INPUT_FILES SET INPUT_FILES=INPUT_FILES;%%X ELSE SET INPUT_FILES=%%X
ECHO %INPUT_FILES%
I'm going to pass %INPUT_FILES% as an input to another executable if I can work out how to fix the issue here.
Edit
If I do the following commands, I can update the INPUT_FILES variable:
IF "%INPUT_FILES%"=="" (SET INPUT_FILES=Test1) ELSE (SET INPUT_FILES=Test1a)
IF "%INPUT_FILES%"=="" (SET INPUT_FILES=Test2) ELSE (SET INPUT_FILES=%INPUT_FILES%;Test2a)
ECHO %INPUT_FILES%
..which outputs:
Test1;Test2a
..but if I try and add that into the FOR loop I get nothing:
CLS
REM #ECHO OFF
SET INPUT_FILES=
for /f %%X in ('dir /b E:\Builds\MyProgram\Release\*.exe E:\Builds\MyProgram\Release\*.dll') do (IF "%INPUT_FILES%"=="" (SET INPUT_FILES=%%X) ELSE (SET INPUT_FILES=%INPUT_FILES%;%%X))
ECHO %INPUT_FILES%
The problem is the expansion of the percent variable.
These are expanded in the moment of parsing (also for blocks).
Better use delayed expansion, the syntax is nearly the same, but with !variable! instead of %variable%.
The difference is: The expansion occurs at execution time not at parsing time.
#ECHO OFF
CLS
setlocal EnableDelayedExpansion
SET "INPUT_FILES="
for /f %%X in ('dir /b E:\Builds\*.exe E:\Builds\*.dll') do (
IF "!INPUT_FILES!"=="" (
SET "INPUT_FILES=%%X"
) ELSE (
SET "INPUT_FILES=!INPUT_FILES!;%%X"
)
)
ECHO !INPUT_FILES!