Skip to main content

CMD Compatibility and CMDebug / TCC-RT, Part 2

After my request for CMD batch files that didn’t work with CMDebug or TCC, a user sent me a batch file with a curious usage of the CMD delayed variable expansion syntax.

CMD supports the !var! syntax to delay variable expansion at the time of a command’s execution, rather than when the statement is read. For example, suppose you want to create a list of all the files in a directory. Your first try might be something like this:

set FILES=

for %f in (*) do set FILES=%FILES% %f

Now, this will work in CMDebug and TCC, but it will not work in CMD. The reason is because CMD actually ends up executing this:

[code]for %f in (*) do set FILES= %f[/code]

and you end up with a list containing only the last file in the directory.

So Microsoft added the option to delay variable expansion in CMD (configurable either at startup or with the SETLOCAL command). It’s not necessary in TCC batch files, but it is supported in TCC and CMDebug for the sake of compatibility.

So since TCC already supported this, what was the incompatibility? The trouble was with this line:

set "regVal=!regVal:%%=?!"

and a corresponding:

set "regVal=!regVal:?=%%!"

What’s the purpose here? The code is substituting a ? for every % in the variable, and later reversing the substitution. Why use delayed variable expansion, since this isn’t an IF or FOR statement or a command group, and the value isn’t going to change between the time the line was read and the line was executed?

Because CMD can’t handle something like this:

set "regVal=%regVal:%%=?%"

It will terminate the argument at the first matching %, so you end up with this:

set "regVal=%regVal:%"

OK, so why not simply escape the % character that you want to substitute?

set "regVal=%regVal:^%=?%"

This works fine in TCC and CMDebug, but fails in CMD. (It might be a bug, or simply unsupported behavior – the CMD documentation doesn’t mention it.) So the developer used delayed variable expansion to prevent the % from being interpreted as enclosing the variable name. When CMD evaluated the line, it found a doubled %, which it converted to a single %, and the line was executed as:

set "regVal=!regVal:%=?!"

So why didn’t this work with TCC? Because TCC didn’t expect to find an unescaped % character inside a substitution string in a delayed expansion variable. Because, why would you do that? No reason — unless you want to substitute % characters in CMD, and you were unable to do it any of the expected ways. (There are several ways to do this in TCC, all of them more understandable than this tortured syntax.)

I have added support for this CMD syntax to the current versions of TCC, TCC-RT, and CMDebug.

Keep sending those wacky CMD batch files!