Wednesday, June 5, 2013

Parsing command line arguments in a batch program (Windows)

Everytime I'd to create a batch to do compilations, or any other kind of stuff I always bumped to the same problem, parsing command line arguments. Usually I ended up with something like:


if "%1"=="-x32" (
   Do something for x32
)
if "%2"=="-x32" (
   Do something for x32
)
if "%1"=="-x64" (
   Do something for x64
)
if "%2"=="-x64" (
   Do something for x64
)

This is really bad way to do it, so I decided to spend sometime today and solve the problem once and for all, as a result I finally have a decent way to parse arguments, and I will document it in here in case I need it again or if someone else is struggling to get this for himself. (I love getopts from Linux! but I guess Windows will keep forcing us to this kind of solutions)

 @echo off  
 setlocal enabledelayedexpansion  
 if [%1] ==[] goto usage  
 call:parseArguments %*  
 if "%x32%" == "true" (  
   echo Well done you set x32 to true  
 )  
 if "%x64%" == "true" (  
   echo Well done you set x64 to true  
 )  
 if NOT "%d" == "" (  
   echo you set the output dir to: %d%  
 )  
 GOTO Exit  


 @rem ================================================================================  
 @rem Functions  
 @rem ================================================================================  
 :usage  
 Echo Usage: %0 [-x32] [-x64] [-d output-dir]  
 goto exit  

 :getArg  
 set valname=%~1  
 echo arg: !%valname%!  
 goto:eof  

 :parseArguments  
 rem ----------------------------------------------------------------------------------  
 @echo off  
 :loop  
 IF "%~1"=="" GOTO cont  
 set argname=%~1  
 set argname=%argname:~1,100%  
 set value=%~2  
 @rem if the next value starts with - then it's a new parameter  
 if "%value:~0,1%" == "-" (  
   set !argname!=true  
   SHIFT & GOTO loop  
 )  
 if "%value%" == "" (  
   set !argname!=true  
   SHIFT & GOTO loop  
 )  
 set !argname!=%~2  
 @rem jumps first and second parameter  
 SHIFT & SHIFT & GOTO loop  

 :cont  
 goto:eof  

 rem ----------------------------------------------------------------------------------  
 :Exit  

The magic ocurrs in the "parseArguments" function, (Oh yes, batch files have functions, I didn't know that until now. This page was really helpful: http://www.dostips.com/DtTutoFunctions.php).

This function contains two things that I learned today. The first one: SHIFT, this command shift the arguments putting the second argument in front and the third in second place, and so on. This is really useful if you don't know the number of arguments, so you only need to do something like the following code to print all the arguments:


 :loop  
 IF "%~1"=="" GOTO cont  
 echo %~1
 SHIFT & GOTO loop  
 :cont  

The second think I learned is how to create dynamic variables, thanks to this guy: http://batcheero.blogspot.com/2007/07/dynamic-variable-name.html, this is useful to create the variables that you're going to use later in your batch program. This is possible because I used: setlocal enabledelayedexpansion at the very beginning of the batch program, otherwise the !thing! won't work.

So the parse arguments function just iterate over the arguments guessing if they were written as: -x32 or -d c:\temp, (in the first case %x32% will be set to true and the former %d% will contain c:\temp.

Here're the results:


> test.bat -d "c:\test dir\blah"
you set the output dir to: c:\test dir\blah
> test.bat -d "c:\test dir\blah" -x32 -x64
Well done you set x32 to true
Well done you set x64 to true
you set the output dir to: c:\test dir\blah

That's it, enjoy