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%
Code: Select all
call :%option% 2>NUL
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="
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 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
Code: Select all
echo The sum of 3 plus 5 is: %EXPR(3+5)%, and 3 times 6 is: %EXPR(3*6)%
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)%
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
Code: Select all
%echo /A% The sum of 3 plus 5 is: {3+5}, and 3 times 6 is: {3*6}
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
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
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)
Code: Select all
%call% lenght=StrLen(This is a string)
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
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)
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)
Code: Select all
%call% X1=:If{b*b-4*a*c geq 0}Then{(-b+sqrt)/2*a}Else{0}
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!
Antonio