Writing Batch code in an easier way with the aid of macros

Discussion forum for all Windows batch related topics.

Moderator: DosItHelp

Message
Author
Aacini
Expert
Posts: 1564
Joined: 06 Dec 2011 22:15
Location: México City, México

Writing Batch code in an easier way with the aid of macros

#1 Post by Aacini » 15 Dec 2012 20:44

The aim of this topic is explain severals methods to write Batch code, from simple examples to very complex segments, in an easier and clearer way with the aid of macros; this technology is somewhat forgotten, so I want to boost its usage a little. This topic also compile many posts about this theme that are scattered at multiple places; to review they, copy the number given in square brackets and change the "t=4083" part at end of the address bar above.

A Batch macro is a variable enclosed in percent-signs that expand its value when the program is executed, in the same way as a regular variable, but that in this case contains code instead of data. A simple example on using data as code is this one:

Code: Select all

set /P option=Enter the desired option [Insert,Delete,Update]: 
goto %option%
Of course, if the input data is not one of these labels the program will issue an error and stop, but you may ignore this error changing GOTO command by CALL:

Code: Select all

call :%option% 2>NUL
This way, if the label does not exist, the program just continue with the line after the CALL.

We may use very simple macros to provide small parts of frequently used commands to make they simpler to use. For example, we may prepare abbreviations for the most common options of FOR command, so they becomes easier to remember:

Code: Select all

set file=
set dir=/D
set line=/F "usebackq delims="
We may use these abbreviations to write FOR commands this way:

Code: Select all

for %file% %%a in (*.*) do echo File: %%a
for %dir%  %%a in (*.*) do echo Dir: %%a
for %line% %%a in (filename.txt) do echo Line: %%a
for %line% %%a in ("filename with spaces.txt") do echo Line: %%a
These very simple macros may aid begginers to start using FOR command with less problems. Later, when they got more experience, they will learn full FOR details (or perhaps they will not, if these forms are enough for their needs!).

In order for a macro to be more useful it must do more things than just replace a value. For example, suppose we want ECHO command display results of arithmetic operations:

Code: Select all

echo The sum of 3 plus 5 is: 3+5, and 3 times 6 is: 3*6
To display this result we firstly need a way to delimit the arithmetic operations from the normal text. We may invent EXPR() macro for this purpose:

Code: Select all

echo The sum of 3 plus 5 is: %EXPR(3+5)%, and 3 times 6 is: %EXPR(3*6)%
However, how we can make these macros return the proper values? Well, a very, very simple solution is this:

Code: Select all

set EXPR(3+5)=8
set EXPR(3*6)=18
echo The sum of 3 plus 5 is: %EXPR(3+5)%, and 3 times 6 is: %EXPR(3*6)%
Please, don't blame me for try to cheat on this point using a crude solution! This may be a suitable solution if we develop a method to define all needed macros in an automatic way, as we will see later!

The way to do more things in a macro is by placing a complete command into it, or even several complete commands; the explanation on how to do that is here: [t=1827]. An even more advanced version is a macro with parameters that may manipulate the text placed after it in the same line: [t=2518&p=12045#p12045]. This feature makes possible to redefine the syntax of Batch elements, that is, to write certain Batch code in a different way: the executed code may be large and complex, but the new syntax be very simple. An example of such a macro is %Set/S% var:~start[,size]=new string "Set Substring" macro that may replace part of a variable value with a new string; the part to replace is given in the standard substring notation: [t=2704]. This way, instead of define a macro to evaluate arithmetic expressions in ECHO command, we may define a new syntax for ECHO command via a macro that add such capability. The new syntax choosen in this case is: %ECHO /A% text {expression} ....

Code: Select all

@echo off

rem ECHO /A extended-command macro
rem Antonio Perez Ayala

rem Evaluate arithmetic expressions enclosed in braces and show the result

setlocal DisableDelayedExpansion
set LF=^


::Above 2 blank lines are required - do not remove
set ^"\n=^^^%LF%%LF%^%LF%%LF%^^"

set echo /A=for %%n in (1 2) do if %%n==2 (%\n%
   rem Get last character of argv %\n%
   set last=0%\n%
   for /L %%a in (12,-1,0) do (%\n%
      set /A "last|=1<<%%a"%\n%
      for %%b in (!last!) do if "!argv:~%%b,1!" equ "" set /A "last&=~1<<%%a"%\n%
   )%\n%
   rem Get start-len pairs of segments that end at right brace %\n%
   set rightBraces=%\n%
   set start=1%\n%
   for /L %%a in (1,1,!last!) do (%\n%
      if "!argv:~%%a,1!" equ "}" (%\n%
         set /A len=%%a-start%\n%
         set rightBraces=!rightBraces! !start!-!len!%\n%
         set /A start=%%a+1%\n%
      )%\n%
   )%\n%
   if !start! leq !last! (%\n%
      set /A len=last-start+1%\n%
      set rightBraces=!rightBraces! !start!-!len!%\n%
   )%\n%
   rem Assemble the output line %\n%
   set output=%\n%
   rem Separate argv in segments at right brace %\n%
   for %%a in (!rightBraces!) do for /F "tokens=1-2 delims=-" %%b in ("%%a") do (%\n%
      rem Divide each segment at left brace %\n%
      for /F "tokens=1,2 delims={" %%d in ("!argv:~%%b,%%c!") do (%\n%
         rem Evaluate right part as expression, if any %\n%
         set expr=%\n%
         if "%%e" neq "" set /A "expr=%%~e"%\n%
         rem Complete output line with left part and expr value %\n%
         set output=!output!%%d!expr!%\n%
      )%\n%
   )%\n%
   rem Show the final output line %\n%
   echo(!output!%\n%
   endlocal%\n%
) else setlocal EnableDelayedExpansion ^& set argv=

setlocal EnableDelayedExpansion

echo Enter lines for ECHO /A Extended-Command macro
echo Syntax: %%ECHO /A%% Text {expression} ...
:nextTest
   echo/
   set line=
   set /P "line=%%ECHO /A%% "
   if not defined line goto :EOF
   %echo /A% %line%
goto nextTest
Previous %ECHO /A% "Extended-Command" macro (to differentiate it from ECHO command) finally provide an adequate solution to our problem:

Code: Select all

%echo /A% The sum of 3 plus 5 is: {3+5}, and 3 times 6 is: {3*6}
Note this interesting side effect of previous test program:

Code: Select all

>echo_mac.bat
Enter lines for ECHO /A Extended-Command macro
Syntax: %ECHO /A% Text {expression} ...

%ECHO /A% One={one=!random!}, Two={two=!random!}, One-Two={one-two}
One=567, Two=22159, One-Two=-21592

%ECHO /A% One={one=!random!}, Two={two=!random!}, One-Two={one-two}
One=15130, Two=9014, One-Two=6116
That is, SET /A commands executed into the macro may create new local variables that may be used in posterior expressions in the same line. This way, the simple ECHO_MAC.BAT example program may serve as a command-line calculator with auxiliary variables and all the capabilities of SET /A command!


An easy way to realize what exactly is being executed in a macro with parameters is extracting the macro code into a separate file this way: "echo !macroName! > macroName.txt" after Delayed Expansion was Enabled; this method will aid on finding errors and also on converting macro code into a normal subroutine.


The following macro example is a variation of CALL command. It uses the same parsing method of %ECHO /A% macro, but in this case it just rearrange the order of the elements in the line, so a clearer way to write a CALL command is possible:

Code: Select all

@echo off

rem CALL extended-command macro
rem Antonio Perez Ayala

rem Change this: %call% [result=]Part1(pa ra m1)Part2(param2)...     (improved syntax)
rem Into this:   call Part1_Part2... "pa ra m1" param2 ... [result=] (normal syntax)

setlocal DisableDelayedExpansion
set LF=^


::Above 2 blank lines are required - do not remove
set ^"\n=^^^%LF%%LF%^%LF%%LF%^^"

set call=for %%n in (1 2) do if %%n==2 (%\n%
   rem If first part is result= variable: extract it %\n%
   set result=%\n%
   for /F "tokens=1* delims==" %%a in ("!argv!") do (%\n%
      if "%%b" neq "" set "result=%%a" ^& set "argv=%%b"%\n%
   )%\n%
   rem Get last character of argv %\n%
   set last=0%\n%
   for /L %%a in (12,-1,0) do (%\n%
      set /A "last|=1<<%%a"%\n%
      for %%b in (!last!) do if "!argv:~%%b,1!" equ "" set /A "last&=~1<<%%a"%\n%
   )%\n%
   rem Get start-len pairs of segments that end at right parentheses %\n%
   set rightParens=%\n%
   set start=0%\n%
   for /L %%a in (0,1,!last!) do (%\n%
      if "!argv:~%%a,1!" equ ")" (%\n%
         set /A len=%%a-start%\n%
         set rightParens=!rightParens! !start!-!len!%\n%
         set /A start=%%a+1%\n%
      )%\n%
   )%\n%
   if !start! leq !last! (%\n%
      set /A len=last-start+1%\n%
      set rightParens=!rightParens! !start!-!len!%\n%
   )%\n%
   rem Assemble subroutine name and parameters %\n%
   set subName=%\n%
   set params=%\n%
   rem Separate argv in segments at right parentheses %\n%
   for %%a in (!rightParens!) do for /F "tokens=1-2 delims=-" %%b in ("%%a") do (%\n%
      rem Divide each segment at left parentheses %\n%
      for /F "tokens=1,2 delims=(" %%d in ("!argv:~%%b,%%c!") do (%\n%
         rem Left part complete subroutine name %\n%
         set subName=!subName!%%d%\n%
         rem Right part is the next parameter, if any %\n%
         if "%%e" neq "" (%\n%
            set paramInQuotes=%%e%\n%
            rem If parameter have spaces... %\n%
            if "!paramInQuotes: =!" equ "%%e" set paramInQuotes=%\n%
            rem ... and not already enclosed in quotes... %\n%
            if "!paramInQuotes:~0,1!" equ ^""^" set paramInQuotes=%\n%
            if defined paramInQuotes (%\n%
               rem ... enclose it in quotes %\n%
               set params=!params! "%%e"%\n%
            ) else (%\n%
               set params=!params! %%e%\n%
            )%\n%
            rem Insert a "param here" mark in subroutine name %\n%
            set subName=!subName!_%\n%
         )%\n%
      )%\n%
   )%\n%
   rem Perform the equivalent call %\n%
   ECHO call !subName!!params!!result!%\n%
   endlocal%\n%
) else setlocal EnableDelayedExpansion ^& set argv=

setlocal EnableDelayedExpansion

echo Enter lines for CALL Extended-Command macro
echo Syntax: %%CALL%% [result=]Part1(param1)Part2(param2)...
:nextTest
   echo/
   set line=
   set /P "line=%%CALL%% "
   if not defined line goto :EOF
   %call% %line%
goto nextTest
Previous %CALL% Extended-Command macro allows to write a subroutine invocation as if you were "filling in the blanks" to complete a meaningful phrase. For example:

Code: Select all

rem Instead of write this line:
call :Show_LinesEndingAt_InFile_  5  "search string"  filename.txt

rem You may write this one:
%call% :Show(5)LinesEndingAt(search string)InFile(filename.txt)
Previous example subroutine at: [t=3801&p=21940#p21940]. %CALL% macro also allows to write the optional output variable before the subroutine name. For example:

Code: Select all

%call% lenght=StrLen(This is a string)
If subroutine names are carefully choosen, a Batch program may be auto-documented and pleasant to read. A particular subroutine that looks so good when it is called this way is the following one:

Code: Select all

@echo off

rem If-Then-Else numeric function, equivalent to C's operator: (condition)?then:else
rem Antonio Perez Ayala

rem Condition must have this form: expr1 compare-op expr2
rem Then_part and else_part may be arithmetic expressions

goto Main

:If_Then_Else_ "condition" then_part else_part [result=]
setlocal EnableDelayedExpansion
set /A "result=%~3"
set "condition=%~1"
for %%c in (lss leq equ neq geq gtr) do (
   if "!condition:%%c=!" neq "%condition%" (
      set /A "#1=!condition:%%c=,#2=!"
      call :compare %%c %2
   )
)
for %%a in (!result!) do endlocal & if "%4" neq "" (set %4=%%a) else echo %%a
exit /B

:compare
if !#1! %1 !#2! set /A "result=%~2"
exit /B

:Main

echo Syntax: If_Then_Else_ "condition" then_part else_part [result=]
:nextTest
   echo/
   set line=
   set /P "line=If_Then_Else_ ="
   if not defined line goto :EOF
   call :If_Then_Else_ !line! listName=
   echo !listName!
goto nextTest
For example:

Code: Select all

rem Instead of write this line:
call :If_Then_Else_ "A gtr B" A B  Max=

rem You may write this one:
%call% Max=:If(A gtr B)Then(A)Else(B)
Note that the parameters can not have nested parentheses. For example:

Code: Select all

rem Instead of write this line:
call :If_Then_Else_ "b*b-4*a*c geq 0" (-b+sqrt)/2*a 0  X1=

rem You can NOT write this one:
%call% X1=:If(b*b-4*a*c geq 0)Then((-b+sqrt)/2*a)Else(0)
However, you may change %CALL% parameter delimiters by a different character (braces, for example), so this form be valid:

Code: Select all

%call% X1=:If{b*b-4*a*c geq 0}Then{(-b+sqrt)/2*a}Else{0}
... or you may change braces by parentheses in the processed parameters:

Code: Select all

%call% X1=:If(b*b-4*a*c geq 0)Then({-b+sqrt}/2*a)Else(0)


We may develop a more advanced macro that parse an arithmetic expression to execute "arithmetic functions" (Batch subroutines) and replace its return value in the expression, in an entirely equivalent way of other programming languages. %SET/A% "arithmetic expressions with functions" is a large macro that was developed with this goal; you may review it here: [t=2704] (below Set Substring macro).

Cool! 8)

Antonio

Aacini
Expert
Posts: 1564
Joined: 06 Dec 2011 22:15
Location: México City, México

Re: Writing Batch code in an easier way with the aid of macr

#2 Post by Aacini » 16 Dec 2012 12:40

You must note that macros with parameters must be executed as complete commands only; they can NOT be used to provide parts for other commands, like simple macros (for technical details, see previous links). For the same reason, it is not possible to use a macro with parameters to modify the syntax of the compound commands IF and FOR. This mean that the desired forms of FOR command shown below can NOT be written:

Code: Select all

%for% %%i=1 to 10 do echo Iterate from 1 to 10: %%i
%for% %%i=10 to 100 step 10 do echo Iterate from 10 to 100 by step 10: %%i
There is no way to circumvent this restriction. However, it is possible to create a list of values based on previous syntax, and then process it in a posterior FOR command. For example:

Code: Select all

%Set numberList% OneToTen=1 to 10
for %%i in (%OneToTen%) do echo Iterate from 1 to 10: %%i

%Set numberList% TenToHundredByTen=10 to 100 step 10
for %%i in (%TenToHundredByTen%) do echo Iterate from 10 to 100 by step 10: %%i
Below is the code of this and other similar macros designed to create lists of values intended to be processed in a FOR command.


Code: Select all

@echo off

rem "Set numberList" values-generator macro
rem Antonio Perez Ayala

rem Generate a list of decimal numbers,
rem filled with left zeros (or trimmed) if DIGITS option is given

setlocal DisableDelayedExpansion
set LF=^


::Above 2 blank lines are required - do not remove
set ^"\n=^^^%LF%%LF%^%LF%%LF%^^"

rem %%Set numberList%% listName=B to E [step S] [digits D]
rem tokens=1-8:        a        b c  d  e    f   g(e)   h(f)

set Set numberList=for %%n in (1 2) do if %%n==2 (%\n%
   for /F "tokens=1-8 delims== " %%a in ("!argv!") do (%\n%
      rem Process parameters %\n%
      set listName=%%a%\n%
      set step=1%\n%
      set digits=%\n%
      if /I "%%e" equ "step" (%\n%
         set step=%%f%\n%
         if /I "%%g" equ "digits" set digits=%%h%\n%
      ) else if /I "%%e" equ "digits" set digits=%%f%\n%
      rem Create the list %\n%
      set numberList=%\n%
      if not defined digits (%\n%
         for /L %%n in (%%b,!step!,%%d) do (%\n%
            set numberList=!numberList! %%n%\n%
         )%\n%
      ) else (%\n%
         for %%g in (!digits!) do (%\n%
            for /L %%n in (%%b,!step!,%%d) do (%\n%
               set number=000000000%%n%\n%
               set numberList=!numberList! !number:~-%%g!%\n%
            )%\n%
         )%\n%
      )%\n%
   )%\n%
   for /F "tokens=1*" %%a in ("!listName!!numberList!") do endlocal ^& set %%a=%%b%\n%
) else setlocal EnableDelayedExpansion ^& set argv=


echo Enter lines for "Set numberList" Values-Generator macro
echo Syntax: %%Set numberList%% listName=B to E [step S] [digits D]
:nextTest
   echo/
   set line=
   set /P "line=%%Set numberList%% listName="
   if not defined line goto :EOF
   %Set numberList% listName=%line%
   echo %listName%
goto nextTest
%Set numberList% listName=B to E [step S] [digits D]
List of decimal numbers, filled with left zeros (or trimmed) at D digits.


Code: Select all

@echo off

rem "Set charList" values-generator macro
rem Antonio Perez Ayala

rem Generate a list of characters from Ascii code B (down)to E
rem individually enclosed in quotes or in a long string if /S switch is given

setlocal DisableDelayedExpansion
set LF=^


::Above 2 blank lines are required - do not remove
set ^"\n=^^^%LF%%LF%^%LF%%LF%^^"

set Ascii= ­"#$%%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~

rem %%Set charList%% listName=B [down]to E [/S]
rem tokens=1-5:      a        b  c       d  e

set Set charList=for %%n in (1 2) do if %%n==2 (%\n%
   rem Process parameters %\n%
   for /F "tokens=1-5 delims== " %%a in ("!argv!") do (%\n%
      set listName=%%a%\n%
      set /A start=%%b-32, step=1, end=%%d-32%\n%
      if /I "%%c" equ "downto" set step=-1%\n%
      set left= ^"%\n%
      set right=^"%\n%
      if /I "%%e" equ "/S" set left=^& set right=%\n%
   )%\n%
   rem Create the list %\n%
   set charList=%\n%
   for /L %%i in (!start!,!step!,!end!) do (%\n%
      set "charList=!charList!!left!!Ascii:~%%i,1!!right!"%\n%
   )%\n%
   for /F "tokens=1*" %%a in ("!listName! "!charList!"") do endlocal ^& set "%%a=%%~b"%\n%
) else setlocal EnableDelayedExpansion ^& set argv=


setlocal EnableDelayedExpansion

set "Ascii=!Ascii!€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿"
set "Ascii=!Ascii!ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ"

echo Enter lines for "Set charList" Values-Generator macro
echo Syntax: %%Set charList%% listName=B [down]to E [/S]
:nextTest
   echo/
   set line=
   set /P "line=%%Set charList%% listName="
   if not defined line goto :EOF
   %Set charList% listName=%line%
   echo !listName!
goto nextTest
%Set charList% listName=B [down]to E [/S]
List of characters from Ascii code B (down)to E individually enclosed in quotes, or in a long string if /S switch is given. Management of Ascii character 33 (! exclamation point) is problematic when Delayed Expansion is Enabled. For simplicity, current version of this macro just change it by Ascii-173 (inverse exclamation point in code pages 437 and 850). You should re-enter Ascii characters 127-255 because they may been modified when they were posted in this site.


Code: Select all

@echo off

rem "Set hexNumList" values-generator macro
rem Antonio Perez Ayala

rem Generate a list of hexadecimal numbers,
rem filled with left zeros (or trimmed) at digits=D (default=4)

setlocal DisableDelayedExpansion
set LF=^


::Above 2 blank lines are required - do not remove
set ^"\n=^^^%LF%%LF%^%LF%%LF%^^"

rem %%Set hexNumList%% listName=B [down]to E [digits D]
rem tokens=1-6:        a        b  c       d  e      f

set Set hexNumList=for %%n in (1 2) do if %%n==2 (%\n%
   rem Get parameter values %\n%
   for /F "tokens=1-6 delims== " %%a in ("!argv!") do (%\n%
      set listName=%%a%\n%
      set /A begin=%%b, first=%%b, end=%%d, step=1, digits=4%\n%
      if /I "%%c" equ "downto" set step=-1%\n%
      if /I "%%e" equ "digits" set digits=%%f%\n%
   )%\n%
   rem Initialize movement of "digits dials" %\n%
   if !step! equ -1 (%\n%
      set hexDigits=0 1 2 3 4 5 6 7 8 9 A B C D E F%\n%
   ) else (%\n%
      set hexDigits=F E D C B A 9 8 7 6 5 4 3 2 1 0%\n%
   )%\n%
   set lastDigit=!hexDigits:~-1!%\n%
   set turnThisDial=!lastDigit!%\n%
   for %%a in (!hexDigits!) do (%\n%
      set nextDigit[%%a]=!lastDigit!%\n%
      set lastDigit=%%a%\n%
   )%\n%
   rem Convert first value to hexa into D8..D1 digits %\n%
   set hexDigits=0123456789ABCDEF%\n%
   for /L %%i in (1,1,8) do (%\n%
      set /A "D=first&0xF, first>>=4"%\n%
      for %%d in (!D!) do set D%%i=!hexDigits:~%%d,1!%\n%
   )%\n%
   rem Generate the requested list %\n%
   set hexNumList=%\n%
   for /L %%n in (!begin!,!step!,!end!) do (%\n%
      rem Insert current number in the list %\n%
      set hexNum=%\n%
      for /L %%i in (1,1,!digits!) do set hexNum=!D%%i!!hexNum!%\n%
      set hexNumList=!hexNumList! !hexNum!%\n%
      rem Turn the dials to next number (like a mechanical odometer) %\n%
      set previousDial=!turnThisDial!%\n%
      for /L %%i in (1,1,!digits!) do (%\n%
         if !previousDial! equ !turnThisDial! (%\n%
            for %%d in (!D%%i!) do set D%%i=!nextDigit[%%d]!%\n%
            set previousDial=!D%%i!%\n%
         )%\n%
      )%\n%
   )%\n%
   for /F "tokens=1*" %%a in ("!listName!!hexNumList!") do endlocal ^& set %%a=%%b%\n%
) else setlocal EnableDelayedExpansion ^& set argv=


echo Enter lines for "Set hexNumList" Values-Generator macro
echo Syntax: %%Set hexNumList%% listName=B [down]to E [digits D]
:nextTest
   echo/
   set line=
   set /P "line=%%Set hexNumList%% listName="
   if not defined line goto :EOF
   %Set hexNumList% listName=%line%
   echo %listName%
goto nextTest
%Set hexNumList% listName=B [down]to E [digits D]
List of hexadecimal numbers, filled with left zeros (or trimmed) at D digits (default=4). Values B and E may be written in hexadecimal format (0x...).


Code: Select all

@echo off

rem "Set randomList" values-generator macro
rem Antonio Perez Ayala

rem Generate a permutation of all numbers in given range
rem or less numbers if NUMBERS option is given

setlocal DisableDelayedExpansion
set LF=^


::Above 2 blank lines are required - do not remove
set ^"\n=^^^%LF%%LF%^%LF%%LF%^^"

rem %%Set randomList%% listName=B to E [numbers N]
rem tokens=1-6:        a        b c  d  e       f

set Set randomList=for %%n in (1 2) do if %%n==2 (%\n%
   rem Get parameter values %\n%
   for /F "tokens=1-6 delims== " %%a in ("!argv!") do (%\n%
      set listName=%%a%\n%
      set /A left=%%b, right=%%d-%%b+1, numbers=right%\n%
      if /I "%%e" equ "numbers" set numbers=%%f%\n%
   )%\n%
   if !numbers! gtr !right! set numbers=!right!%\n%
   rem Generate vector of numbers in natural order %\n%
   for /L %%i in (1,1,!right!) do (%\n%
      set number[%%i]=!left!%\n%
      set /A left+=1%\n%
   )%\n%
   rem Take off N numbers from the vector in random order %\n%
   set /A left=1, last=right-numbers+1%\n%
   set randomList=%\n%
   for /L %%n in (!right!,-1,!last!) do (%\n%
      rem Take off one number at random position %\n%
      set /A "index=(%%n*!random!>>15)+left, center=(left+right)/2"%\n%
      for /F "tokens=1-3" %%i in ("!left! !index! !right!") do (%\n%
         set randomList=!randomList! !number[%%j]!%\n%
         rem If picked number is in first half... %\n%
         if %%j leq !center! (%\n%
            rem ... move last number to empty slot, and eliminate it %\n%
            set number[%%j]=!number[%%k]!%\n%
            set /A right-=1%\n%
         ) else (%\n%
            rem ... move first number to empty slot, and eliminate it %\n%
            set number[%%j]=!number[%%i]!%\n%
            set /A left+=1%\n%
         )%\n%
      )%\n%
   )%\n%
   for /F "tokens=1*" %%a in ("!listName!!randomList!") do endlocal ^& set %%a=%%b%\n%
) else setlocal EnableDelayedExpansion ^& set argv=


echo Enter lines for "Set randomList" Values-Generator macro
echo Syntax: %%Set randomList%% listName=B to E [numbers N]
:nextTest
   echo/
   set line=
   set /P "line=%%Set randomList%% listName="
   if not defined line goto :EOF
   %Set randomList% listName=%line%
   echo %listName%
goto nextTest
%Set randomList% listName=B to E [numbers N]
List of all random numbers with values between B and E with no repetitions (permutation), or just N numbers.


Code: Select all

@echo off

rem "Set dirList" values-generator macro
rem Antonio Perez Ayala

rem Generate a list of directory names enclosed in quotes
rem or separated by semicolon in a long string if /S switch is given

setlocal DisableDelayedExpansion
set LF=^


::Above 2 blank lines are required - do not remove
set ^"\n=^^^%LF%%LF%^%LF%%LF%^^"


goto Main


rem SplitArgv: separate argv into individual parameters and identify options.

rem At input: optv vector have the options to identify, from element 1 to optc;
rem if there are not options to identify, set optc=0.

rem At output: optv vector elements have the positions of each option found;
rem if an option was not found it have the same value of next element, or argc+1.

:SplitArgv argv
set /A opti=1, argc=0
:nextArg
   rem Separate next parameter
   if "%~1" equ "" goto endArgv
   set /A argc+=1
   set argv[%argc%]=%1
   rem Match parameter vs. options given
   for /L %%i in (%opti%,1,%optc%) do (
      if /I "!optv[%%i]!" equ "%~1" (
         for /L %%j in (!opti!,1,%%i) do (
            set optv[%%j]=%argc%
         )
         set /A opti=%%i+1
      )
   )
   shift
   goto nextArg
:endArgv
rem Set rest of options to "not found"
for /L %%j in (%opti%,1,%optc%) do (
   set /A optv[%%j]=argc+1
)
exit /B


:Main

rem %%Set dirList%% listName=[wildCard ...] [/R [path]] [/X wildCard ...] [/S]

set Set dirList=for %%n in (1 2) do if %%n==2 (%\n%
   rem Split argv string into individual parameters %\n%
   set optv[1]=/R%\n%
   set optv[2]=/X%\n%
   set optv[3]=/S%\n%
   set optc=3%\n%
   call :SplitArgv !argv!%\n%
   set listName=!argv[1]!%\n%
   rem Assemble list of wildcards to include %\n%
   set include=*%\n%
   set /A inclEnd=optv[1]-1%\n%
   if !inclEnd! gtr 1 (%\n%
      set include=%\n%
      for /L %%i in (2,1,!inclEnd!) do (%\n%
         set include=!include! !argv[%%i]!%\n%
      )%\n%
   )%\n%
   rem Get /R recursive path, if any %\n%
   set recPath=%\n%
   if !optv[1]! lss !optv[2]! (%\n%
      set recPath=/R%\n%
      set /A opt_R=optv[1]+1%\n%
      if !opt_R! lss !optv[2]! (%\n%
         for %%i in (!opt_R!) do set recPath=/R !argv[%%i]!%\n%
      )%\n%
   )%\n%
   rem Assemble list of wildcards to /X exclude, if any %\n%
   set exclude=%\n%
   set /A exclBegin=optv[2]+1, exclEnd=optv[3]-1%\n%
   for /L %%i in (!exclBegin!,1,!exclEnd!) do (%\n%
      set exclude=!exclude! !argv[%%i]!%\n%
   )%\n%
   rem Get /S string switch %\n%
   set left= ^"%\n%
   set right=^"%\n%
   if !optv[3]! equ !argc! set left=^& set right=;%\n%
   rem Insert included names, delete excluded names %\n%
   if not defined recPath (%\n%
      for /D %%a in (!include!) do set name[%%a]=1%\n%
      for /D %%a in (!exclude!) do set name[%%a]=%\n%
   ) else (%\n%
      pushd !recPath:~3!%\n%
      for /R /D %%a in (!include!) do set name[%%a]=1%\n%
      for /R /D %%a in (!exclude!) do set name[%%a]=%\n%
      popd%\n%
   )%\n%
   rem Generate the list of names %\n%
   set dirList=%\n%
   for /F "tokens=2 delims=[]" %%a in ('set name[ 2^^^>NUL') do (%\n%
      set dirList=!dirList!!left!%%a!right!%\n%
   )%\n%
   for /F "tokens=1*" %%a in ("!listName! !dirList!") do endlocal ^& set %%a=%%b%\n%
) else setlocal EnableDelayedExpansion ^& set argv=


setlocal EnableDelayedExpansion

rem %%Set dirList%% listName=[wildCard ...] [/R [path]] [/X wildCard ...] [/S]

echo Enter lines for "Set dirList" Values-Generator macro
echo Syntax: %%Set dirList%% listName=[wildC ...] [/R [path]] [/X wildC ...] [/S]
echo/
echo --^> Enter EOF to end
:nextTest
   echo/
   set line=
   set /P "line=%%Set dirList%% listName="
   if /I "!line:~0,3!" equ "EOF" goto :EOF
   %Set dirList% listName=%line%
   echo %listName%
goto nextTest
%Set dirList% listName=[wildCard ...] [/R [path]] [/X wildCard ...] [/S]
List of directory names enclosed in quotes, or separated by semicolon in a long string if /S switch is given, that match wildCards (default=*), /R into the whole path, /X exclude next wildCards.


For simplicity, previous macros does not check for any errors; if wrong parameters are given, wrong results will be obtained. You may insert an equal-sign instead a space in the macro parameters if you wish, to improve readability. Note that these macros use the same rudimentary approach of first %EXPR(...)% macro shown above, that is, define each macro value immediately before use it; in this case this does not look inappropriate as before because the created value is a list of items.

Previous macros show a diversity of methods to get a certain result. %Set numberList% use a simple method to process a variable number of parameters that is adequate when they are few. %Set dirList% (and %Set fileList% in a major degree) use a more advanced method to process variable number of parameters via :SplitArgv subroutine; in these cases, all Batch standard delimiters (,;=) may be included in the parameters as separators. You may also include these delimiters in previous macros if you wish to make them standard in this respect.

You must note that the processing of macro parameters is usually more complex than the generation of the result itself; this depends on how apart is the new macro syntax from the equivalent normal Batch code. A large example of this point is %Set fileList% macro (below), and an even larger one is %SET/A% macro previously mentioned. However, as more complex a macro is, more powerful it is.

Very cool! 8) :)

Antonio

Aacini
Expert
Posts: 1564
Joined: 06 Dec 2011 22:15
Location: México City, México

Re: Writing Batch code in an easier way with the aid of macr

#3 Post by Aacini » 19 Dec 2012 21:14

Code: Select all

@echo off

rem "Set fileList" values-generator macro
rem Antonio Perez Ayala

rem Generate a list of file names in quotes

rem Julian Day Number documentation: http://www.hermetic.ch/cal_stud/jdn.htm

setlocal DisableDelayedExpansion


rem Change these values to match your locale:
set dateFormat=MM/DD/YY
rem (preserve DD MM YY words, just change its order and the separator)
set daysOfWeek=Mon Tue Wed Thu Fri Sat Sun
set monthNames=Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec


set dateSep=%dateFormat:~2,1%
call set dateOrder=%%dateFormat:%dateSep%= %%

set LF=^


::Above 2 blank lines are required - do not remove
set ^"\n=^^^%LF%%LF%^%LF%%LF%^^"


goto Main


rem SplitArgv: separate argv into individual parameters and identify options.

rem At input: optv vector have the options to identify, from element 1 to optc;
rem if there are not options to identify, set optc=0.

rem At output: optv vector elements have the positions of each option found.
rem If an option was not found, it have the same value of next element;
rem if not found option is the last one, it have argc+1.

:SplitArgv argv
set /A opti=1, argc=0
:nextArg
   rem Separate next parameter
   if "%~1" equ "" goto endArgv
   set /A argc+=1
   set argv[%argc%]=%1
   rem Match parameter vs. options given
   for /L %%i in (%opti%,1,%optc%) do (
      if /I "!optv[%%i]!" equ "%~1" (
         for /L %%j in (!opti!,1,%%i) do (
            set optv[%%j]=%argc%
         )
         set /A opti=%%i+1
      )
   )
   shift
   goto nextArg
:endArgv
rem Set rest of options to "not found"
for /L %%j in (%opti%,1,%optc%) do (
   set /A optv[%%j]=argc+1
)
exit /B


:Main


set exclam=!

rem %%Set fileList%% listName=[wildCard ...] [/R [path]] [/X wildCard ...] [/D dateSpec] [/Z size]
rem %%Set fileList%% listName=[fileName ...] [/$ pathsVar]

set Set fileList=for %%n in (1 2) do if %%n==2 (%\n%
   rem Split argv string into individual parameters %\n%
   set optc=0%\n%
   for %%a in (/$ /R /X /D /Z) do (%\n%
      set /A optc+=1%\n%
      set optv[!optc!]=%%a%\n%
   )%\n%
   call :SplitArgv !argv!%\n%
   rem PHASE 1: Process parameters %\n%
   set listName=!argv[1]!%\n%
   rem Assemble list of wildcards to include %\n%
   set include=*.*%\n%
   set /A inclEnd=optv[1]-1%\n%
   if !inclEnd! gtr 1 (%\n%
      set include=%\n%
      for /L %%i in (2,1,!inclEnd!) do (%\n%
         set include=!include! !argv[%%i]!%\n%
      )%\n%
   )%\n%
   rem Get /$ pathsVar to search, if any %\n%
   if !optv[1]! lss !optv[2]! (%\n%
      set /A opt_P=optv[1]+1%\n%
      for %%i in (!opt_P!) do for %%p in (!argv[%%i]!) do set pathsVar=!%%p!%\n%
      rem Special case: Insert included names here... %\n%
      for %%a in (!include!) do set name[%%~$pathsVar:a]=1%\n%
      set name[]=%\n%
      rem ... and ignore rest of parameters %\n%
   ) else (%\n%
REM Begin of else part of: if /$ pathsVar ... %\n%
REM Indent margin omitted until next matching REMark %\n%
   rem Get /R recursive path, if any %\n%
   set recPath=%\n%
   if !optv[2]! lss !optv[3]! (%\n%
      set recPath=/R%\n%
      set /A opt_R=optv[2]+1%\n%
      if !opt_R! lss !optv[3]! (%\n%
         for %%i in (!opt_R!) do set recPath=/R !argv[%%i]!%\n%
      )%\n%
   )%\n%
   rem Assemble list of wildcards to /X exclude, if any %\n%
   set exclude=%\n%
   set /A exclBegin=optv[3]+1, exclEnd=optv[4]-1%\n%
   for /L %%i in (!exclBegin!,1,!exclEnd!) do (%\n%
      set exclude=!exclude! !argv[%%i]!%\n%
   )%\n%
   rem Assemble testing of /D date specification, if any %\n%
   set testLimits=%\n%
   if !optv[4]! lss !optv[5]! (%\n%
      set /A opt_D=optv[4]+1%\n%
      rem Test if given dateSpec is pattern matching %\n%
      for %%i in (!opt_D!) do (%\n%
         for /F "tokens=1-3" %%b in ("%dateOrder%") do (%\n%
            for /F "tokens=1-3 delims=%dateSep%" %%e in ("!argv[%%i]!") do (%\n%
               set %%b=%%e^& set %%c=%%f^& set %%d=%%g%\n%
            )%\n%
         )%\n%
      )%\n%
      set datePattern=1%\n%
      for %%a in (%dateOrder%) do (%\n%
         if "!%%a!" equ "" set datePattern=%\n%
      )%\n%
      if defined datePattern (%\n%
         rem Assemble first part of testing expression (date pattern matching) %\n%
         set testLimits=test=1%\n%
         for %%a in (%dateOrder%) do (%\n%
            if "!%%a!" neq "*" (%\n%
               if "!%%a:~0,1!" equ "0" set %%a=!%%a:~1!%\n%
               set "testLimits=!testLimits!*!exclam!(%%a-!%%a!)"%\n%
            )%\n%
         )%\n%
         rem Initialize month names used in pattern matching %\n%
         set i=0%\n%
         for %%a in (%monthNames%) do (%\n%
            set /A i+=1%\n%
            set %%a=!i!%\n%
         )%\n%
      ) else (%\n%
         rem Separate given days range in first-last limits %\n%
         for %%i in (!opt_D!) do for /F "tokens=1-2 delims=-" %%a in (" !argv[%%i]!-99999") do (%\n%
            set first=%%a%\n%
            if "%%a" equ " !argv[%%i]!" (set last=%%a) else set last=%%b%\n%
         )%\n%
         rem Identify type of date limit requested (daysOld or daysOfWeek) %\n%
         set daysOldType=1%\n%
         for %%a in (first last) do (%\n%
            for %%b in (!%%a!) do if "!daysOfWeek:%%b=!" neq "%daysOfWeek%" set daysOldType=%\n%
         )%\n%
         rem Assemble first part of testing expression (calculate JulianDayNumber) %\n%
         set "testLimits=A=(MM-14)/12,JDN=(1461*(YY+4800+A))/4+(367*(MM-2-12*A))/12-(3*((YY+4900+A)/100))/4+DD-32075"%\n%
         rem Assemble second part of testing expression... %\n%
         if defined daysOldType (%\n%
            rem Get today's Julian Day Number %\n%
            for /F "tokens=1-3" %%b in ("%dateOrder%") do (%\n%
               for /F "tokens=1-3 delims=%dateSep%" %%e in ("%date%") do (%\n%
                  rem Modify next line if YY in dateFormat is not the last field %\n%
                  set /A %%b=1%%e %% 100, %%c=1%%f %% 100, %%d=%%g%\n%
               )%\n%
            )%\n%
            set /A !testLimits!, today=JDN%\n%
            rem ... (file's date in range of daysOld) %\n%
            set "testLimits=!testLimits!, days=today-JDN, test=((!first!-days)*(days-!last!)>>31)+1"%\n%
         ) else (%\n%
            rem Initialize daysOfWeek names used in subranges %\n%
            set i=0%\n%
            for %%a in (%daysOfWeek%) do (%\n%
               set %%a=!i!%\n%
               set /A i+=1%\n%
            )%\n%
            rem ... (file's date in range of daysOfWeek) %\n%
            set "testLimits=!testLimits!, dow=JDN%%7, test=((!first!-dow)*(dow-!last!)>>31)+1"%\n%
         )%\n%
      )%\n%
   )%\n%
   rem Assemble testing of /Z size, if any %\n%
   if !optv[5]! lss !argc! (%\n%
      rem Separate given size range in first-last limits %\n%
      set /A opt_S=optv[5]+1%\n%
      for %%i in (!opt_S!) do for /F "tokens=1-2 delims=-" %%a in (" !argv[%%i]!-0x7FFFFFFF") do (%\n%
         set first=%%a%\n%
         if "%%a" equ " !argv[%%i]!" (set last=%%a) else set last=%%b%\n%
      )%\n%
      rem Convert KB and MB tags %\n%
      set /A KB=1024,MB=KB*KB%\n%
      for %%a in (first last) do if /I "!%%a:~-1!" equ "B" set /A %%a=!%%a:~0,-2!*!%%a:~-2!%\n%
      rem Assemble next part of testing expression (file's size in given range) %\n%
      if not defined testLimits set testLimits=test=1%\n%
      set "testLimits=!testLimits!, first=1,A=!first!-size,first=!exclam!((A+1)/A)"%\n%
      set "testLimits=!testLimits!, last=1,A=size-!last!,last=!exclam!((A+1)/A), test*=first*last"%\n%
   )%\n%
   rem PHASE 2: Get the result %\n%
   rem Insert included names %\n%
   if not defined testLimits (%\n%
      if not defined recPath (%\n%
         for %%a in (!include!) do set name[%%a]=1%\n%
      ) else (%\n%
         pushd !recPath:~3!%\n%
         for /R %%a in (!include!) do set name[%%a]=1%\n%
         popd%\n%
      )%\n%
   ) else (%\n%
REM ECHO TEST: [!testLimits!]%\N%
      if not defined recPath (%\n%
         for %%a in (!include!) do (%\n%
            for /F "tokens=1-3" %%b in ("%dateOrder%") do (%\n%
               for /F "tokens=1-3,6 delims=%dateSep% " %%e in ("%%~TZa") do (%\n%
                  rem Modify next line if YY in dateFormat is not the last field %\n%
                  set /A %%b=1%%e %% 100, %%c=1%%f %% 100, %%d=%%g, size=%%h%\n%
               )%\n%
            )%\n%
            set /A !testLimits! 2^>NUL%\n%
            rem At this point, "test" variable is 0 or 1 %\n%
REM ECHO %%a: [%%~TZa] [DAYS=!days!] [DOW=!dow!] [TEST=!test!]%\N%
            for %%b in (!test!) do set name[%%a]=!test:~0,%%b!%\n%
         )%\n%
      ) else (%\n%
         pushd !recPath:~3!%\n%
         for /R %%a in (!include!) do (%\n%
            for /F "tokens=1-3" %%b in ("%dateOrder%") do (%\n%
               for /F "tokens=1-3,6 delims=%dateSep% " %%e in ("%%~TZa") do (%\n%
                  rem Modify next line if YY in dateFormat is not the last field %\n%
                  set /A %%b=1%%e %% 100, %%c=1%%f %% 100, %%d=%%g, size=%%h%\n%
               )%\n%
            )%\n%
            set /A !testLimits! 2^>NUL%\n%
            rem At this point, "test" variable value is 0 or 1 %\n%
REM ECHO /R=%%a:%\N%
REM ECHO [%%~TZa] [DAYS=!days!] [DOW=!dow!] [TEST=!test!]%\N%
            for %%b in (!test!) do set name[%%a]=!test:~0,%%b!%\n%
         )%\n%
         popd%\n%
      )%\n%
   )%\n%
   rem Delete excluded names %\n%
   if defined exclude (%\n%
      if not defined recPath (%\n%
         for %%a in (!exclude!) do set name[%%a]=%\n%
      ) else (%\n%
         pushd !recPath:~3!%\n%
         for /R %%a in (!exclude!) do set name[%%a]=%\n%
         popd%\n%
      )%\n%
   )%\n%
REM Indent margin omitted up to this point %\n%
REM End of else part of: if /$ pathsVar ... %\n%
   )%\n%
   rem Generate the list of names %\n%
   set fileList=%\n%
   for /F "tokens=2 delims=[]" %%a in ('set name[ 2^^^>NUL') do (%\n%
      set fileList=!fileList! "%%a"%\n%
   )%\n%
   for /F "tokens=1*" %%a in ("!listName!!fileList!") do endlocal ^& set %%a=%%b%\n%
) else setlocal EnableDelayedExpansion ^& set argv=


setlocal EnableDelayedExpansion


echo Enter lines for "Set fileList" Values-Generator macro
echo Syntax: listName=[wildC ...] [/R [path]] [/X wildC ...] [/D dateSpec] [/Z size]
echo or:     listName=[name ...]  [/$ pathsVar]
echo/
echo --^> Enter EOF to end
:nextTest
   echo/
   set line=
   set /P "line=%%Set fileList%% listName="
   if /I "!line:~0,3!" equ "EOF" goto :EOF
   %Set fileList% listName=%line%
   echo/%listName%
goto nextTest
%Set fileList% listName=[wildCard ...] [/R [path]] [/X wildCard ...] [/D dateSpec] [/Z size]
%Set fileList% listName=[fileName ...] [/$ pathsVar]

List of file names in quotes that match wildCards (default=*.*), /R into the whole path, /X exclude next wildCards, /D with given date specification, /Z with given size.

Here are several examples:

Code: Select all

%Set fileList% ImageFiles=*.bmp *.jpg *.png *.img
%Set fileList% AllMyTextFilesButTheseNot=*.txt /R "%HOMEPATH%" /X TheseNot.*
The dateSpecification may have one of three possible forms:

1- Range of days old: #1-#2. For example:

Code: Select all

%Set fileList% FilesBetween30And60DaysOld=/D 30-60
%Set fileList% Files30DaysOldOrOlder=/D 30-
%Set fileList% FilesNewerOrEqualThan30DaysOld=/D -30
%Set fileList% FilesCreatedYesterday=/D 1
%Set fileList% FilesCreatedToday=/D 0

2- Range of days of week: dow1-dow2 (3-letter names). For example:

Code: Select all

%Set fileList% FilesCreatedDuringTheWeek=/D -Fri
%Set fileList% FilesCreatedOnWeekend=/D Sat-
%Set fileList% FilesCreatedOnTuesday=/D Tue

3- Date pattern matching: MM/DD/YYYY (asterisks match any value). For example:

Code: Select all

%Set fileList% FilesReceivedTheFirstDayOfEachMonthThisYear=/D */1/2012
%Set fileList% FilesReceivedOnMyBirthdays=/D Sep/18/*
%Set fileList% OldFilesFromYear2000=/D */*/2000

The same range scheme is used in /Z size option. You may use KB or MB tag at end of each number (with no space) to indicate larger size units; the maximum allowed size is 2047MB. For example:

Code: Select all

%Set fileList% TextFilesBetween1And4MegaBytes=*.txt /Z 1MB-4MB
%Set fileList% AComplexFileList=*csv *.xml /R /X theseNot.* norThese.* /D -7 /Z 100KB-

The "/$ pathsVar" switch allows to quickly locate specific files (not groups of them selected with wildcards) via a pathsVariable that contain a list of directories where the seek will be performed. For example:

Code: Select all

%Set fileList% LocationOfPrograms=explorer.exe wmic.exe more.exe more.com /$ path

Although /R switch allows to seek for files into a whole subdirectory tree, it usually takes more time than /$ switch; if the total number of files inside the target folder is large, the time difference between both methods may be huge! The list of subdirectories inside a target folder can be easily created via %Set dirList% macro with /S switch, and this list can be created just one time and used in many /$ seeks. For example, instead of:

Code: Select all

%Set fileList% Location=WantedFile.txt /R targetFolder
... you may use:

Code: Select all

%Set dirList% allSubfolders=/R targetFolder /S
%Set fileList% Location=WantedFile.txt /$ allSubfolders
If several files needs to be searched this way, it is faster to include all of them in one /$ execution than repeatedly execute a /$ seek with just one file.

You should use simple file names with /$ switch, not wildcards; if you use a wildcard, each existent filename directly selected by the wildcard will be seek in the directories stored in pathsVar (in the same order) and the location of the first one found is returned. Any switch placed after /$ will be ignored.


Super-cool!! 8) :D

Antonio

Aacini
Expert
Posts: 1564
Joined: 06 Dec 2011 22:15
Location: México City, México

Re: Writing Batch code in an easier way with the aid of macr

#4 Post by Aacini » 03 Jan 2013 14:50

We may use the same method of %ECHO% and %CALL% macros above to write a simpler version of %SET/A% macro that replace the output value of Batch functions in an aritmethic expression.

Code: Select all

@echo off

rem SET /A extended-command macro
rem Antonio Perez Ayala

rem Replace the result of Batch functions in an aritmethic expression
rem %%SET /A%% result=expr...:function(param1 "pa ra m2" param3)...expr

setlocal DisableDelayedExpansion
set LF=^


::Above 2 blank lines are required - do not remove
set ^"\n=^^^%LF%%LF%^%LF%%LF%^^"

set set /A=for %%n in (1 2) do if %%n==2 (%\n%
   set argv=!argv!,%\n%
   rem Get last character of argv %\n%
   set last=0%\n%
   for /L %%a in (12,-1,0) do (%\n%
      set /A "last|=1<<%%a"%\n%
      for %%b in (!last!) do if "!argv:~%%b,1!" equ "" set /A "last&=~1<<%%a"%\n%
   )%\n%
   rem Get start-len pairs of segments that end at commas %\n%
   set commas=%\n%
   set start=0%\n%
   for /L %%a in (0,1,!last!) do (%\n%
      if "!argv:~%%a,1!" equ "," (%\n%
         set /A len=%%a-start%\n%
         set commas=!commas! !start!-!len!%\n%
         set /A start=%%a+1%\n%
      )%\n%
   )%\n%
   if !start! leq !last! (%\n%
      set /A len=last-start+1%\n%
      set commas=!commas! !start!-!len!%\n%
   )%\n%
   rem Execute sub-expressions (assignments) separated by commas %\n%
   for %%a in (!commas!) do for /F "tokens=1-2 delims=-" %%b in ("%%a") do (%\n%
      set "subExpr=!argv:~%%b,%%c!"%\n%
      rem Get start-len pairs of segments that start at colon %\n%
      set colons=%\n%
      set start=0%\n%
      for /L %%d in (0,1,%%c) do (%\n%
         if "!subExpr:~%%d,1!" equ ":" (%\n%
            set /A len=%%d-start%\n%
            set colons=!colons! !start!-!len!%\n%
            set /A start=%%d+1%\n%
         )%\n%
      )%\n%
      if defined colons if !start! lss %%c (%\n%
         set /A len=%%c-start%\n%
         set colons=!colons! !start!-!len!%\n%
      )%\n%
      rem Invoke Batch functions separated by colons, if any %\n%
      if defined colons (%\n%
         set expr=%\n%
         for %%d in (!colons!) do for /F "tokens=1-2 delims=-" %%e in ("%%d") do (%\n%
            set segment=!subExpr:~%%e,%%f!%\n%
            if not defined expr (%\n%
               rem First segment is the variable assignment %\n%
               set "expr=!segment!"%\n%
            ) else (%\n%
               rem Divide next segments as :funcName(params)rest %\n%
               for /F "tokens=1,2* delims=()" %%g in ("!segment!") do (%\n%
                  rem First part is Batch function, second part is parameters %\n%
                  call :%%g %%h #=%\n%
                  rem Replace function result and rest of segment %\n%
                  set "expr=!expr!!#!%%i"%\n%
               )%\n%
            )%\n%
         )%\n%
         set "subExpr=!expr!"%\n%
      )%\n%
      rem Execute the assignment %\n%
      set /A "!subExpr!"%\n%
      rem Get the variable name %\n%
      for /F "delims==" %%d in ("!subExpr!") do set variable=%%d%\n%
   )%\n%
   for %%a in (!variable!) do for %%b in (!%%a!) do endlocal ^& set %%a=%%b%\n%
) else setlocal EnableDelayedExpansion ^& set argv=


setlocal EnableDelayedExpansion

echo Enter lines for SET /A Extended-Command macro
echo Syntax: %%SET /A%% result=expr...:function(param1 "pa ra m2" param3)...expr

:nextTest
   echo/
   set line=
   set /P "line=%%SET /A%% result="
   if not defined line goto :EOF
   %set /A% result=!line!
   echo result=%result%
goto nextTest


:times10 n r=n*10
set /A "%2=(%~1)*10"
exit /B
%set /A% var=expr...:function(param1 "pa ra m2" param3)...expr

Previous macro is called "%set /A%" Extended Command macro, but you may rename it if you wish. A Batch function is a subroutine that return its output value in the variable given in last parameter. For simplicity, %set /A% macro is designed with certain constraints in the expression.

1- A function start with colon and enclose parameters in parentheses, but parameters can not have nested parentheses nor be separated by commas (use spaces or semicolons instead):

Code: Select all

   :function((a+b)*c)                     Wrong!
   :function(param1,"pa ra m2",param3)    Wrong!
   :function(param1 "pa ra m2" param3)    Right

2- The expression is comprised of several variable assignments separated by commas that will be executed from left to right:

Code: Select all

%set /A% var1=expr1, var2=expr2, ...

3- If you need to use parentheses in a function parameter, assign the desired value to an auxiliary variable and use it in the function:

Code: Select all

%set /A% var=(a+b)*c, result=(x+y)/z+:function(var)*(b-d)

4- All variables used in the expression are deleted when the macro ends, excepting the last one that preserve its final value.

Previous program include a simple :times10 function to achieve some tests, but you must include in the same file all functions used by %set /A% macro, or slightly modify it in order to invoke external functions. For example:

Code: Select all

Enter lines for SET /A Extended-Command macro
Syntax: %SET /A% result=expr...:function(param1 "pa ra m2" param3)...expr

%SET /A% result=(2+1)*3+:times10(5*2)*3
result=309
You may review a rich set of Batch functions designed to achieve a variety of calculations in the Library of functions to be used in former SET/A macro.


Extra-cool!!! 8) :P

Antonio

brinda
Posts: 78
Joined: 25 Apr 2012 23:51

Re: Writing Batch code in an easier way with the aid of macr

#5 Post by brinda » 02 Aug 2013 19:20

antonio,
thanks. as always for me after rereading a few times again and again - some minor enlightment on knowing what is batch macro and it uses are :) . All the while i was thinking it has something to do with office macro etc. probably need to go a few more times before checking on dave's jeb's topic in macro. ed's macro is more like light years away right now.

bocsi6
Posts: 5
Joined: 01 Oct 2018 08:48

Re: Writing Batch code in an easier way with the aid of macros

#6 Post by bocsi6 » 09 Nov 2018 09:44

Hi, Aacini,
You wrote as a simple batch macro :

set /P option=Enter the desired option [Insert,Delete,Update]:
goto %option%

Why did you have to use
Goto %option%
instead of simply
%option% ?

Thanks for answer
Greets bocsi6

Squashman
Expert
Posts: 4013
Joined: 23 Dec 2011 13:59

Re: Writing Batch code in an easier way with the aid of macros

#7 Post by Squashman » 09 Nov 2018 09:55

bocsi6 wrote:
09 Nov 2018 09:44
Hi, Aacini,
You wrote as a simple batch macro :

set /P option=Enter the desired option [Insert,Delete,Update]:
goto %option%

Why did you have to use
Goto %option%
instead of simply
%option% ?
The value of the variable option is not a valid command.

bocsi6
Posts: 5
Joined: 01 Oct 2018 08:48

Re: Writing Batch code in an easier way with the aid of macros

#8 Post by bocsi6 » 09 Nov 2018 10:31

Hi,
You wrote me : The value of the variable option is not a valid command.
Why is it not a valid command, as to call a batch macro the usual form is %macro% ?
In this case why is %option% form not valid ?
Thanks for answer

Squashman
Expert
Posts: 4013
Joined: 23 Dec 2011 13:59

Re: Writing Batch code in an easier way with the aid of macros

#9 Post by Squashman » 09 Nov 2018 10:45

bocsi6 wrote:
09 Nov 2018 10:31
Hi,
You wrote me : The value of the variable option is not a valid command.
Why is it not a valid command, as to call a batch macro the usual form is %macro% ?
In this case why is %option% form not valid ?
Thanks for answer
Insert, Delete and Update are NOT VALID commands. They do not EXIST! Try typing them at the command prompt and all three of them will tell you they are not recognized as an Internal or External command.

Even in macro form, if you need to execute the macro the first word within the macro has to be a valid internal or external command. That should be pretty obvious for ANY programming language.

bocsi6
Posts: 5
Joined: 01 Oct 2018 08:48

Re: Writing Batch code in an easier way with the aid of macros

#10 Post by bocsi6 » 09 Nov 2018 11:02

Many thanks bocsi6

bocsi6
Posts: 5
Joined: 01 Oct 2018 08:48

Re: Writing Batch code in an easier way with the aid of macros

#11 Post by bocsi6 » 09 Nov 2018 14:49

Hi, Aacini,
Here I am again with a newer question.
I read on the same site :

1.line set file=
2.line for %file% %%a in (*.*) do echo File: %%a

1.line : as I know with
set file=
command I can delete the <file> variable, that is <file> variable already does not exist (not defined).

And in the 2.line you refer to it in %file% form.

How is this ?
After the 1.line the <file> variable is defined or not ?
Does it exist or not ?

Please explain this.
Thanks

Aacini
Expert
Posts: 1564
Joined: 06 Dec 2011 22:15
Location: México City, México

Re: Writing Batch code in an easier way with the aid of macros

#12 Post by Aacini » 09 Nov 2018 16:22

@bocsi6,

Mmmm... Your question is out of context. You extracted parts of the original post and then asked a question about such separate parts...

This is the original post:
Aacini wrote:
15 Dec 2012 20:44
We may use very simple macros to provide small parts of frequently used commands to make they simpler to use. For example, we may prepare abbreviations for the most common options of FOR command, so they becomes easier to remember:

Code: Select all

set file=
set dir=/D
set line=/F "usebackq delims="
We may use these abbreviations to write FOR commands this way:

Code: Select all

for %file% %%a in (*.*) do echo File: %%a
for %dir%  %%a in (*.*) do echo Dir: %%a
for %line% %%a in (filename.txt) do echo Line: %%a
for %line% %%a in ("filename with spaces.txt") do echo Line: %%a
In this example, file, dir and line are "very simple macros" (abbreviations) that "provide small parts of" FOR command. The example use of file macro is this:

Code: Select all

for %file% %%a in (*.*) do echo File: %%a
... and the standard way to use a FOR command to process files is this:

Code: Select all

for %%a in (*.*) do echo File: %%a
... so it should be obvious that file variable must be empty or not defined in order for this to work...




Independently of that, all Batch file users with some experience know that this command:

Code: Select all

set file=
... is used to delete or undefine the file variable, and I am pretty sure that you could find a ton of examples about this point in several Batch file users forums, like this or this or this or this in this site, or this or this, etc in S.O. site...

I suggest you to get more experience in Batch file programming or search for specific answers about not "macros in Batch files" related questions in other places before post a new question here...

Antonio

bocsi6
Posts: 5
Joined: 01 Oct 2018 08:48

Re: Writing Batch code in an easier way with the aid of macros

#13 Post by bocsi6 » 10 Nov 2018 03:52

Hi, Aacini,
Excuse me for these pretty stupid questions, but I am just on the way to get more experience
in Batch file programming. I know that these ones do not belong to this forum, but in connection
with this theme I can read on the net a lot of rubbish ideas and I wanted to get really proper
answers from real experts.
Excuse me once more.
Many thanks for the answers.
Greets bocsi6

siberia-man
Posts: 110
Joined: 26 Dec 2013 09:28
Contact:

Re: Writing Batch code in an easier way with the aid of macros

#14 Post by siberia-man » 12 Nov 2018 23:55

Hi Aacini,

A quite exciting technique for scripting batch macros. One this I can't understand. When you code the beginning of any macro you use something like this:

Code: Select all

set echo /A=for %%n in (1 2) do if %%n==2 (%\n%
...
   endlocal%\n%
) else setlocal EnableDelayedExpansion ^& set argv=
I think that comparison with 1 is the more logical. More over. The loop can be reduced until the only pass, isn't it?

Code: Select all

set echo /A=for %%n in (1) do (%\n%
   setlocal EnableDelayedExpansion ^& set argv=
...
   endlocal%\n%
)
Please correct me if I am wrong.

jeb
Expert
Posts: 884
Joined: 30 Aug 2007 08:05
Location: Germany, Bochum

Re: Writing Batch code in an easier way with the aid of macros

#15 Post by jeb » 13 Nov 2018 00:45

Hi siberia-man,

I currently can't follow Aacini's technic :?: , but it looks impressive ( I have to spend more time on this).

But I can answer your question.
siberia-man wrote:
12 Nov 2018 23:55
A quite exciting technique for scripting batch macros. One this I can't understand. When you code the beginning of any macro you use something like this:

Code: Select all

set echo /A=for %%n in (1 2) do if %%n==2 (%\n%
...
endlocal%\n%
) else setlocal EnableDelayedExpansion ^& set argv=

I think that comparison with 1 is the more logical. More over. The loop can be reduced until the only pass, isn't it?
No, it can't.
The idea is to call the "set argv= ..." part only once the argv (when %%n == 1), that is to avoid double execution of macros appended with & like in

Code: Select all

%$myStrLenMacro% myResultVar myStringVar & echo !myResultvar!
See the discussion at
Macros with parameters appended

jeb

Post Reply