Wednesday, March 31, 2010

"Fun" With Batch Files

Here's how to compute the root Program Files directory of the Visual Studio 2008 installation from the VS90COMNTOOLS environment variable... (for example...) Obviously: same technique may apply to other env vars and other programs.

The Line of Code
In a batch file:
for /f "usebackq delims=" %%d in (`echo "%VS90COMNTOOLS%\..\.."`) do set VS_ROOT_DIR=%%~fd

Directly in a command prompt:
for /f "usebackq delims=" %d in (`echo "%VS90COMNTOOLS%\..\.."`) do set VS_ROOT_DIR=%~fd

The only difference between the two is the doubling up of the "%%d" percents when referencing for loop variables. Don't ask why, just learn: that's the way it is. Actually, if you want to ask why and then go figure out the answer... that would be a good blog post for you to write.

The Dissection
On my machine, the command...
echo "%VS90COMNTOOLS%\..\.."

...produces:
"C:\Program Files (x86)\Microsoft Visual Studio 9.0\Common7\Tools\\..\.."

Nice. Accurate and all, but look at all the ugliness we've produced. Doubled up backslashes, enclosing double quotes, ..s, not to mention the length.

So. That fancy "for /f" line. Let's clean up that output to remove the ugliness.

Using "for /f" with the "usebackq" option allows you to put a command inside backticks, as in `echo something`, and capture the output of that command in the for loop variable. In this dead simple example, the for loop would iterate exactly once, and the value "something" would be in the loop variable.

Using the "delims=" option allows you to split the output by delimiter characters. When you say "delims=" with the equal sign right up against the closing double quote, that means: no delimiter characters, give me the full output all at once. Much different result than "delims= " with a space in between... that one loops over the output separating by space characters, giving multiple for loop iterations based on how many spaces are in the output. You could also say "delims=\" to split at the path separator character, or "delims= \/:" to split at common date time separators.

Now that usebackq and delims are well understood, or at least explained, how does that help us get rid of the ugliness? Well... it's mostly the magical "%~fd" at the very end of our friendly line of code that makes the universe beautiful again. The usebackq/delims pain we went through was really just a way to get the string we want into a for loop variable so we can take advantage of for loop variable expansion modifiers.

for /f "usebackq delims=" %d in (`echo "%VS90COMNTOOLS%\..\.."`) do set VS_ROOT_DIR=%~fd

The "%~fd" says this: give me the value of the loop variable %d, and while you're at it, remove any enclosing double quotes (~), and resolve it to a full path, assuming the variable represents a file or directory name (f).

The net effect of all this is that our line of code...
for /f "usebackq delims=" %d in (`echo "%VS90COMNTOOLS%\..\.."`) do set VS_ROOT_DIR=%~fd

Finally, in the end, simply evaluates to the line of code we wanted to write in the first place, but without hard coding a machine specific path name in a batch file:
set VS_ROOT_DIR=C:\Program Files (x86)\Microsoft Visual Studio 9.0

Type "help for" in a Windows command prompt for more of the gory, sickening details regarding loop variable expansion.

And help keep the universe beautiful. Even if you still have to write batch files once in a while.

1 comment:

Rob said...

Hey there Mr. Batchfile.

I've told stories to folks over the years about your "status report" batch file/scripts/utilities you built during our Visio days to do our annoying weekly reports.

That would make for a nice case study. :-)