Read Mode

Batch Macros | Fastest piece of reusable code | By Sounak@9434

Batch Macros | The Fastest piece of reusable code: We all use reusable code in our batch files, don't we? And we all like our batch files to be fast and efficient. But "Call :label [arguments]" and "goto :label" statement slows down the execution of batch files. Though it's not noticeable in most  of the cases. But, in performance driven codes the speed of execution is a very vital point. So we are going to learn a new type of reusable code, which will boost the speed of your batch programmers.

Batch Macros | Fastest piece of reusable code | By Sounak@9434
For making Your Batch programs faster than usual, You need to have the basic knowledge of the following things in Batch. And, by changing the normal style of coding with little advance style. You can understand the Batch Programming language in much more deeper way.

And before beginning to learn the hidden beauty of batch macros at first we have to express our gratitude to the creators of this awesome technique. Our huge appreciation goes to Ed DyreenJeb and Dave Benham for the discovery and also to Aacini for his awesome explanation over here.

Now, Before we start understanding about the Batch Macros. We need a tool that can calculate the speed of  execution of your application. Tools like ptime.exe and upct.exe is always helpful but we are gonna use pure batch. We will use a modified version of dbenham's code from an SO post to safely calculate the execution time of our script. 
Make an 'Analog Clock' in CMD using Batch Programming | By Kvc
The main plus-point of using a pure batch code is that it does not need to call a separate file (i.e. External command) which slows down the execution and puts a slight effect on the result. In the other hand the code we are going to use, will calculate the time through two 'SET' commands and then calculate the difference between them through timediff.cmd.

Don't worry, as Always download links are also Provided within this article. So, Stay Calm - And, Enjoy! 😄

What is a Macro?

Let's start with what is a Macro? Macro is a piece of reusable code. Currently there are two main ways of creating batch macros. One is through the doskey command and the other is through defining a variable. Out of both, I prefer using the Variable method. Because, it has its own benefits. But, An Examples worth thousand words. So, here is the most basic example.

set @hw=echo Hello world!
Output>> Hello world!
Congratulation! You just made your first batch macro. It's not much but enough for a cheers.🙋
Now, what is the most wanted thing of any command? If you have guessed right it's arguments. We can't just have commands that does nothing or only does work with the thing it was build to work.

Let's try building a macro command that accepts arguments.

set @hello=echo I'm robot C-3PO. Hello master:
%@hello% Sounak
Output>> I'm robot C-3PO. Hello master: Sounak

Cheers 😁. Now our macros takes arguments as well. They are not that good but well we can fix it. But before going forward the conclusion is that, any argument given after the macro (i.e. %@hello% Sounak) is placed after the source of the macro. Thus the command became "echo I'm robot C-3PO. Hello master: Sounak".

Creating Dynamic Macros

But we can't always have arguments at the end. What if we wanted to echo "Hello Sounak, I have prepared coffee for you." But, By our current knowledge about macros - It is impossible for us to do so, but Jeb discovered an awesome method to do this. We will be using a set command at the end of our macro. Which will take the variable and then echo the output to the screen.

Let's try it.
N.B. Using "^&" instead of "&" is necessary. Otherwise, the remaining won't get set into the macro-variable and will be executed during the macro setup. So, To avoid macro's pre-execution... We, need to keep few things in mind.

set @coffee=echo Hello %user%, I have prepared coffee for you^&set user=
%@coffee% Sounak
echo %user%
Output[1]>> Hello , I have prepared coffee for you
Output[2]>>  Sounak

We can see our Data(Sounak) is indeed set to variable(user) but where is our variable in the macro? If you have followed through our How to use SETLOCAL & ENDLOCAL + DelayedExpansion in batch you will know that variables like %var% will be executed during when cmd reads the line. So %user% gets executed when cmd creates the macro. Run this code to understand.

set @coffee=echo Hello %user%, I have prepared coffee for you^&set user=
set @coffee
Output>> @coffee=echo Hello , I have prepared coffee for you&set user=

See, there's no variable in our macro. So we have to use delayedexpansion to setup our macro with a variable. But there is another problem which we have overlooked last time. In our macro echo command with a variable is executed before the set command. 

In this case we'll get a blank reply(again) even if we use delayedexpansion. But we can't use the set command before echo. Because if we do that the arguments will be placed after the echo command and will come to no use. So we have to do something else. So, that 'set var=' is executed before 'echo blah blah' and also 'set var=' is placed at the end of the command. Here comes the Legendary and Revolutionary trick Created by Jeb. We are going to use for command to set the order of execution of our commands.

setlocal enabledelayedexpansion
set @coffee=for %%z in (1 2) do if %%z equ 2 (echo Hello!user!, Here is your coffee) ELSE set user=
%@coffee% Sounak
Output>> Hello, Here is your coffee

Whoa! Isn't That Output Wrong?

Don't worry as the output is incorrect. But at first we should try to understand what is happening in this trick to get the arguments. When the for command runs it runs the script for two times. Once with value of %%z as 1 and after that once with value of %%z as 2. 

On the first run the code block after that, if check is not run as the value of %%z is 1 and thus it takes the arguments into user (variable) which is the else part. The next time the line executes with %%z as 2 and thus it runs the part in the parentheses and it executes our required line.
Okay, okay, I understand that the output is incorrect so you must be thinking "WHAT HAPPENED NOW?" The answer is pretty simple. !user! got expanded during the variable setup. Run 'set @coffee' after the code to understand. So the conclusion is, We can't use delayedexpansion during the macro setup. But we would need delayedexpansion when we'll run the macro. There are  basically, two solutions to this problem.

Solution For Creating Right Macros

After a Lot of failures of creating a Batch macro. We are finally near to our solution of the problem. Beacuse, Only this can lead us to the new level of knowledge about the batch programming. And, Helps in making our batch programs better & Faster.

Solution 1:-

::Using disabledelayedexpansion while macro setup and after that using delayedexpansion each time the macro is called (less recommended). 

setlocal disabledelayedexpansion
set @coffee=for %%z in (1 2) do if %%z equ 2 (echo Hello!user!, Here is your coffee) ELSE set user=
setlocal enabledelayedexpansion
%@coffee% Sounak
Output>> Hello Sounak, Here is your coffee

Solution 2:-

::Using disabledelayedexpansion during macro setup and use auto delayedexpansion inside the macro (more recommended as endlocal will ensure a fresh use of macro each time).

setlocal disabledelayedexpansion
set @coffee=for %%z in (1 2) do if %%z equ 2 (echo Hello!user!, Here is your coffee^&endlocal) ELSE setlocal enabledelayedexpansion^&set user=
%@coffee% Sounak
Output>> Hello Sounak, Here is your coffee

You must be thinking what is the use of this type of thing as generally you don't get to fetch coffee with batch file. So, here are some examples of single line macros you can use in your batch file.

Choice macro:-

::This macro takes arguments, runs the choice commands with those arguments and returns the pressed key instead of errorlevel. This may become more useful in many cases than, writing and interpretting the output by writing multiple lines of code.

setlocal disabledelayedexpansion
set @choice=for %%z in (1 2) do if %%z equ 2 (choice /c !choices:~1!^&for %%x in (!errorlevel!) do (set _a=!choices:~1,%%x!^&echo !_a:~-1!)^&endlocal) ELSE setlocal enabledelayedexpansion^&set choices=

Input>> T
Output>> T

Randint macro(3 digit):-

::This macro takes arguments and returns a random number between 2 provided 3 digit numbers.
::Providing digits in base of 1000 (using 3 number) is necessary.

setlocal disabledelayedexpansion
set @randint=for %%z in (1 2) do if %%z equ 2 (set /a "dif=!num:~5,3!-!num:~1,3!+1"^&set /a "rand=!random! %% !dif!"^&set /a "fin=!num:~1,3!+!rand!"^&echo !fin!^&endlocal) ELSE setlocal enabledelayedexpansion^&set num=
%@randint% 000 100

Output>> 77 (may be different in your case)

So what are you waiting for start building your macros now. Here are a few ideas, which may help you out in your personal batch project.
  1. Take a number as argument and generate it's table.
  2. Create a macro that takes two numbers as argument (Use the method I used in randint macro {for now} ) and calculates the sum of all the numbers between them.

Result & Conclution

Finally, we are going to compare the macro results with call commands to understand the speed difference. I'm not providing source code for these files here but they are available under the zip archive at the bottom of this page. As choice macro takes input so that one cannot be used for right judgement. Instead, we used Randint macro to test the speed. We are calling it for 1000 times.

And here is the unbelievable result of my PC. May be different in your case, depends on your PC Specifications.

Randint MacroRandint Call (Same file)Randint Call (separate file)
Result1.97 sec12.54 sec13.28 sec

So, finally we can create our own single line macros. In our next topic, we'll learn how to create multi-line macros and how to create macros with 1+ arguments to replace call :label in most cases. For any query, You can leave a comment below this article. And, Me And my team will try to help you out. Thanks for your time. Happy Coding!

Keep Learning, Keep Sharing...
Be Happy, Spread Happiness...

Download Link: Download Macros

No comments:

Powered by Blogger.