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.

On or and its various incarnations

Well, starting from “Or” considered harmful. we noticed that the distinction of andor, xor and ewok is certainly a useful concept, however, the naming scheme leaves room for improvement. Andor is certainly too long for practical usage and ewok always raises associations to small furry creatures.

On the quest for appropriate names we thought that or certainly suffices for andor, just like (mathematical) logic tells us. Xor is short and pronounceable enough for everyday use and the meaning is clear with the usual knowledge of geeks. This leaves ewok. sh suggested eor, which may be interpreted as ewok-or. It does not sound too stupid, is short enough and thus quite usable.

The only challenge now is to switch my writing and talking habits over to those new words :-)

Windows Batch which(1)

Ever wondered which exact executable will be executed when running a command from the command line? UNIXes and Linux have which(1) which tells that. There are implementations on Windows, but not one in batch language I was aware of :-)

So that naturally called for ugly things to be done. I wrote this a while ago and noticed that it does not always works correctly on Windows Vista. At the time of its writing I was working with Windows 2000 and it worked pretty well there. Somehow something is messing with extensions:
C:\>which which
C:\Users\Johannes Rössel\SVN\Projekte\CMD\which\which.cmd
obviously works.
C:\>which which.cmd
doesn't. Contrary to that
C:\>which wget
does not work, but
C:\>which wget.exe
C:\Users\Johannes Rössel\Apps\wget.exe
does. For things in the system's own paths (which are searched first) the above problems do not seem to apply:
C:\>which explorer
C:\Windows\explorer.exe

C:\>which explorer.exe
C:\Windows\explorer.exe
Weird, indeed. But currently I lack time to investigate further. I know it worked pretty well once, maybe I look into it again some day. Until then it's just another random example of Windows Command Processor perversion :-)

UPDATE (2008–06–01): I found another bug that manifests itself most prominently on Windows Vista x64, concerning paths with closing parentheses in them (as happens when installing x86 applications there). That means I have to do a bit escaping as soon as those paths show up in the argument of a FOR loop. Using ! and FOR /F seems to work somehow, except that I only get a single token from that.

Windows Batch Sieve of Eratosthenes

A while ago a fellow student of mine held a “lecture” for pupils on programming where I should help helping the kids. Prime numbers are a nice topic for introductory courses, since they are easy to understand and a simple search for them is written in a few lines of code. I suggested mentioning the Sieve of Eratosthenes as well as giving an implementation of it, since I think the algorithm is pretty easy to understand and nicely fast in generating the first n primes. The programming language we used was Pascal and I was able to come up with a naïve implementation in a manner of minutes. Glancing over the program again I thought it wouldn't be that hard to implement in CMD, so I tried … there was still plenty of time left so I wrote the following perversion of batch language :-)

It's actually very short and I was surprised that it took only that few lines to write. We start by allocating a bunch of n boolean array elements:
set n=%1
for /l %%i in (2,1,%n%) do set l%%i=1
and then we simply iterate over them and delete all those environment variables that resemble multiples of the current number:
:sieve
set /a begin=%1
set /a max=%n% / %1
for /l %%k in (%begin%,1,%max%) do set /a foo=%1 * %%k && set l!foo!=
goto :EOF
and finally we simply output those that are left:
for /l %%i in (2,1,%n%) do if DEFINED l%%i echo %%i
That actually already was most of the code, the rest is merely auxiliary.Now that I look at that code again the sieve loop could as well be
for /l %%k in (%begin%,%1,%max%) do set l!foo!=
This should work too and we save a variable assignment during the inner loop. But I'm too lazy to do benchmarks now so I just leave it the old way that is tried to work.

There is a practical limit for n, though. The batch runs fine for 100 or even 1000 numbers, but sieving 20000 numbers already takes about a minute on my notebook. And there was a point at which the environment seemed to grow too large to handle in a sensible way.

And no, it does not compute all this in parallel, though I wonder how hard it would be to create batch files that calculate things concurrently in multiple processes; might actually make use of all those multicore systems out there. But I don't have a good idea currently how they should communicate if need arises; files are an option but slow and subject to races … well, I still have a lot of lectures I only attend physically, so my mind may be free to solve these problems :-)

Windows Batch Dice Roller

Something I've written a while back now (February 2007): A dice roller, written as a Windows batch file. It supports an arbitrary number of dice (well, up to certain limits of the shell, like 32 bit integers and strings of about 32000 characters maximum) and arbitrarily many sides (up to … you'll get the picture). So if you want to roll, say … 1200 dice with 462 sides each you may do so:
C:>roll 1200d462
The results are then spewn onto the console in the order they were rolled. No sorting yet, I couldn't get myself to implement even simple sorting algorithms, though I may revisit this in future.

This innocent batch file sports a finite state machine for parsing the parameters which may be given by the following regular expression:

[1-9][0-9]*[WDwd][1-9][0-9]*(\+[1-9][0-9]*)?

I allow both German and English notation (w vs. d as separator of number and sides of the dice) as well as optional adding of all rolls and a constant (for Shadowrun 3 initiative rolls). Due to the nature of parsing the user will get pretty precise feedback where a parsing error occurred and what was expected (Hmm, this may call for a general batch FSM generation tool :).

A few limitations, however:

  • Rolling is slow, so 1000 rolls will take a while.
  • Since CMD only knows 32 bit signed integers, the maximum number of dice is 2147483647.
  • Since the results are stored in a single environment variable which are limited to 32764 characters, the maximum practical number of dice reduces further. Otherwise the results will be truncated.
  • Since CMD's random number generator will only yield random numbers between 0 and 32767 the maximum practical number of sides is 32768, otherwise, no roll will yield a result above 32768.
  • Since capping rolls to the number of sides is done by a modulo operation, the results won't be equally distributed if the number of sides is no divisor of 32768 (might be especially noticeable above 16384).

The code is nearly undocumented, but that just adds to the fun :-)

Solving the wrong problem

I faintly remember the times when games and other software came along with installation instructions (nowadays it seems most publishers assume that people can install software without instructing them). A common and recurring template was “Insert the CD into your CD ROM drive. The setup program should start automatically, if it doesn’t, follow these steps to turn on AutoPlay and try again: …”

This is, essentially, a non-solution. It solves a problem the customer doesn’t even have: Usually you don’t think ‘How could I turn on AutoPlay which I disabled a few weeks ago to save me from setups popping up?’ instead you want to run the setup that simply didn’t start automatically (which may be on purpose).

A similar situation occurred to me recently when I visited a web page that wanted to display an image in a popup. I have set my popup blocker to highly aggressive, so it blocks essentially everything that opens a new window. When I manually opened the link that caused the blocked popup in a new tab (a method that usually yields the content) I found myself on a page that explained in detail how I could either turn on Javascript or turn off my popup blocker.

Great. They are solving a problem I don’t even have. I just want to access the content.

Since the advent of popup blockers I doubt popups are a valid method of conveying information to the user anymore.

Syndicate content