Batch tricks

Batch tricks: Recursion

Aah, recursion, the favorite pet of every programmer. Surely it must be possible to recurse even in Windows batch files (I'm still trying to prove Turing-completeness, by the way :-)).

The first tentative test would be an infinite recursion:

@ECHO OFF
:JUMP
CALL :JUMP

And know what? It works. Well, kinda:

******  B A T C H   R E C U R S I O N  exceeds STACK limits ******
Recursion Count=599, Stack Usage=90 percent
******       B A T C H   PROCESSING IS   A B O R T E D      ******

But that's ok, we didn't expect this to do anything useful except of causing a stack overflow. But as we can see, cmd has a stack of some sort and seemingly manages it well enough to allow for recursion.

Time for another test, this time something remotely practical: Factorials. Never mind that those are more easily done with iteration, we want to make sure that recursion works properly:

@ECHO OFF
SETLOCAL ENABLEEXTENSIONS ENABLEDELAYEDEXPANSION

CALL :fac %1
ECHO %RETURN%

:end
ENDLOCAL
GOTO :EOF

:fac
IF %1==0 (
        SET RETURN=1
        GOTO :EOF
)
SET /A TEMP=%1 – 1
CALL :fac %TEMP%
SET /A RETURN*=%1
GOTO :EOF

We need a temporary variable at the end, unfortunately, since cmd does not allow computations inline. But aside from that it looks pretty much how it should. The case for breaking the recursion is also provided in the form of an IF block (sorry, no functional programming niceties, like different function definitions).

But does it work? Oh, sure it does:

> fac 5
120
> fac 10
3628800

And my calculator tells me that those are actually correct. 12! is unfortunately the highest factorial we can compute with it, since we are limited to 32-bit signed integers. A minor bug is still present when using negative numbers, though (infinite recursion, again). This is corrected in the attached version (as well as giving a helpful hint when running the batch without arguments).

Just as a side note, a fun way to implement factorial calculation by leveraging cmd's own “calculator”:

@ECHO OFF
SETLOCAL ENABLEEXTENSIONS ENABLEDELAYEDEXPANSION
SET TEMP=1
FOR /L %%i IN (1,1,%1) DO SET TEMP=!TEMP! * %%i
SET /A TEMP=%TEMP%
ECHO %TEMP%
ENDLOCAL

We simply construct the complete term and evaluate that by using SET /A. Nothing fancy, but probably faster than the recursion.

Batch tricks: Creating libraries

You can call other batch files via the call command and pass parameters as well. Well, suppose you have a plethora of useful functions but you don't want a batch file for each of them or copy them into all batches that use them.

With a simple pattern you can concentrate them into one batch file. I've prepared this here, along with a bit of stuff you might need to do to ensure it's working:

rem jump target, so we can shift without hesitation
set target=%1
rem throw away target to give subroutines access to %1, …
shift
rem prepare a human-friendly list of parameters
call :get_param_list %
goto %target%
:foo
echo Foo called with parameters: %PARAMS%
goto :EOF
:bar
echo Bar called with parameters: %PARAMS%
goto :EOF
:get_param_list
rem account for the first one which has to be discarded due to %

shift
rem we need delayed expansion, but only here
setlocal enableextensions enabledelayedexpansion
:get_param_list_loop
set PARAMS=%PARAMS%, %1
shift
if not%1”=="" goto get_param_list_loop
rem strip the first comma
set PARAMS=%PARAMS:~2%
endlocal & set PARAMS=%PARAMS%
goto :EOF

The code above consists of a bit initialization code, namely the set target=%1 and shift to get the label we want to jump to and return everything to normal for the following code. The get_param_list is mainly there so we have some weird code to look at and have a bit of debugging stuff in place for testing. The rest are labels that mark the subroutines, ending with a goto :EOF each. The batch itself is nothing more than some kind of switch statement, selecting the function to execute with the first parameter.

Calling a function is pretty easy, then (assuming our batch above was named lib.cmd):
call lib foo param1 param2 param3 …
which will yield the following output:
Foo called with parameters: param1, param2, param3, …

Voilà, we got some kind of libraries or namespaces. If you want, you can nest them, I leave that as an exercise for the reader.

Batch tricks: Passing Variables out of Subroutines

Creating subroutines in batch files is easy. Just create a label, a goto :EOF and call :label. However, if the subroutine uses a setlocal-endlocal block you might want to be able to pass variable changes out of that block and still retain the local scope to avoid pollution of the upper scope.

You can exploit the fact that cmd expands variables on reading a line before actually executing it:

setlocal
rem some stuff here
endlocal & set OUTER_VARIABLE=%INNER_VARIABLE%

The trick here is that variable expansion takes place before endlocal (or the set following it) is executed and set is executed after endlocal and thus affects the outer scope, but is able to pass a value of the inner scope along.

Note: I've seen this trick before at Paul Sadowski's site and found it worth remembering, although I didn't use it that much so far (my Bignum implementation will, however make extensive use if it).

Batch tricks: Arrays

Arrays are frequently used in programming and nearly all programming languages have something that can be used as such, nevermind what it's called. Arrays, tables, lists, etc. are all just a means of organizing large amounts of similar data.

In batch files we have to do a little trickery to achieve something one might call arrays. There are at least two possibilities that spring to my mind instantly:

Numerous variables

The easiest and most flexible solution I've come up with so far is simply to create a variable for each entry, all of them share a common prefix (which may be empty). So, to create an array with, say, 100 members, this can be done quickly with

for /l %%i in (1, 1, 100) do set ARRAY%i=0

That way we have an array, called ARRAY (the prefix, I usually use it as a name) with 100 variables in it, each initialized to 0.
Array access is simple in just accessing the appropriate variable, it has, however, a few issues:

  • Accessing the value of an array element that is known at write-time works as intended:

    echo %ARRAY2%
  • Accessing an arbitrary value requires delayed expansion:

    echo !ARRAY%NUM%!


    for /l %%i in (3, 1, 5) do echo !ARRAY%%i!

    Using %ARRAY%NUM%% won't work since cmd's parser gets confused with the %. And % variables will be expanded when the line is read, not when it's executed.
  • Accessig array values for calculations works without delayed expansion (since it doesn't require the % around variable names):

    set /a SUM=ARRAY%X%+ARRAY%Y%

If you insist, you can also use a notation familiar with other programming languages, [ and ] are perfectly legal as characters in environment variables (if I remember correctly the only illegal characters are = and the null byte).

One long string

If you know which characters your array variables will contain you can also use a long string with a separator character:

set ARRAY=1,2,3,4,5,6,7,8,9,10

This requires you to use for /f whenever you need to access a value within the array, write-access to values is very inconvenient (essentially you have to either search for the right indices within the string and do substring magic or you rebuild the entire array each time you change or delete a value.

To sum it up: I always used the first variant, since it's pretty easy and depending on your application deleting values (and tedious copying) might not be necessary or even wanted (my Sieve of Eratosthenes simply kept only the values it was interested in and used if defined to check for them.

Batch tricks: Swapping values

A common task in many algorithms is swapping two values. Usually implentations take the form
temp := a;
a := b;
b := temp;

In some languages (such as Python or Lua) you may also write the following:

a, b = b, a

And a method similar to this one is even possible in batch files.

Since variable substitution in CMD is done while the line is parsed (for backwards compatibility) except when using ! instead of % we can use this to our advantage and swap to values in one line without resorting to a temporary variable:

set A=%B%&set B=%A%

Be careful not to use a space before the & to avoid having that space character in your value (if this is critical).

And the use of % here is also important. Since delayed expansion (done with ! expands when the value is used the code set A=!B!&set B=!A! will set both A and B to the value of B.

Batch tricks: Reversing text

If there is one thing the Windows Command Processor (cmd.exe) can do (except starting other programs) it's string processing. Not at Perl's level but certainly more sohpisticated than the C standard library (not counting regex here).

I was just playing around a bit and came up with this:

@echo off
setlocal enableextensions enabledelayedexpansion
set "DATA=%*"
:loop
set REVDATA=%REVDATA%%DATA:~-1%
set DATA=%DATA:~0,-1%
if defined DATA goto loop
echo %REVDATA%
endlocal

Pretty straightforward, as usual. The obligatory setlocal with the usual options (I almost always set them, regardless whether I need them or not). Saving all command line arguments into a string and then dissecting it character by character. As soon as the original string is eaten up we can quit and output the result.

And it works in Unicode, too:

T:\>reverse ↔¾Ω∞()‡‼ αβγδ да ◙
◙ ад δγβα ‼‡)(∞Ω¾↔

Batch tricks: Breaking loops

When writing a loop sometimes it can become necessary to break out of the loop before it is finished. Batch files allow loops, but do they allow some kind of break statement?

Actually, yes:

@echo off
for /l %%i in (1, 1, 100) do if %%i GTR 10 (goto break) else (echo %%i)
goto :EOF
:break
echo successfully broken the loop

This is the whole file. When run it will print the numbers from 1 to 10 and then break out of the loop, displaying the appropriate message. The code itself should be pretty straightforward. The if statement checks for the loop variable being greater than 10 and based on that will either break or print the number. And as you can see, if we would let the loop finish we wouldn't see any message since we quit immediately after the loop.

Does this work with subroutines as well? Sure:

@echo off
for /l %%i in (1, 1, 100) do if %%i GTR 10 (goto break) else (call :loop %%i)
goto :EOF
:break
echo successfully broken the loop
goto :EOF
:loop
echo %1
goto :EOF

Not much harder, actually. Be careful, though, that the breaking happens in the loop itself and not in the subroutine. A subroutine ends when the end of file is encountered (either due to the end of file or a goto :EOF), so when branching out of the subroutine we would print "successfully broken the loop" but return to looping directly thereafter.

Syndicate content