<http://www.netikka.net/tsneti/info/tscmd040.htm>
Copyright © 2003-2012 by Prof. Timo Salmi  
Last modified Wed 20-Jun-2012 22:11:06

 
Assorted NT/2000/XP/.. CMD.EXE Script Tricks
From the html version of the tscmd.zip 1cmdfaq.txt file
To the Description and the Index
 

This page is edited from the 1cmdfaq.txt faq-file contained in my tscmd.zip command line interface (CLI) collection. That zipped file has much additional material, including a number of detached .cmd script files. It is recommended that you also get the zipped version as a companion.

Please see "The Description and the Index page" for the conditions of usage and other such information.



40} How to get the number of and parse the arguments given to a script?

This task is not quite as trivial as it first appears to be. There are a couple of subtle pitfalls. Since the solution uses shift it "destroys" the parameters from later usage, so they have to be stored. The logic is that if one wants to count the parameters, one is likely also to want to use the parameters! Also note the usage of [] in the if testing lest there is confusion with parameters potentially enclosed in quotes. If one needs a parameter count, it is logical and sensible to always put it as the first thing into the script. The denotations in the example below have clear UNIX connotations.

Take one:
  @echo off & setlocal enableextensions enabledelayedexpansion
  set /a argc=0
  :_argcLoop
    if [%1]==[] goto _exitargcLoop
    set /a argc+=1
    set arg%argc%=%1
    shift
    goto _argcLoop
  :_exitargcLoop
  ::
  :: Show the results

  echo The number of parameters is %argc%
  set /a count_=0
  :_dispLoop
    set /a count_+=1
    if defined arg%count_% (echo !arg%count_%! & goto _dispLoop)
  ::
  endlocal & goto :EOF
An example:
  D:\TEST>cmdfaq one two "the third"
  The number of parameters is 3
  one
  two
  "the third"

If you wish to omit the quotes (") substitute
  set arg%argc%=%~1

Take two:
  @echo off & setlocal enableextensions enabledelayedexpansion
  set argc=0
  :_loop
    if "%~1"=="" goto _next
    set /a argc+=1
    set argv[!argc!]=%~1
    shift
    goto _loop
  :_next
  ::
  echo argc=%argc%
  for /l %%i in (1,1,%argc%) do echo argv[%%i]=!argv[%%i]!
  endlocal & goto :EOF

The output might be e.g.
  C:\_D\TEST>cmdfaq a b c "d e"
  argc=4
  argv[1]=a
  argv[2]=b
  argv[3]=c
  argv[4]=d e
Note a catch. After you apply the above, you no longer can access the original parameters. If you need them later in the batch, you have to store them at the outset of the batch file like in the above.

Let's consider a more complicated extraction of command line information:
  @echo off & setlocal enableextensions enabledelayedexpansion
  ::
  :: The task:
  :: Get switches and ordinary parameters given to a script.
  :: Distinguish between the two categories
  ::

  set argc=0
  :_loop
    if "%~1"=="" goto _next
    set /a argc+=1
    set argv[!argc!]=%~1
    shift
    goto _loop
  :_next
  ::
  echo argc=%argc%
  for /l %%i in (1,1,%argc%) do echo argv[%%i]=!argv[%%i]!
  ::
  set paramCount=0
  set switchCount=0
  for /l %%i in (1,1,%argc%) do (
    echo.!argv[%%i]!| find "/">nul
    if !errorlevel! EQU 0 (
      set /a switchCount+=1
      set switch[!switchCount!]=!argv[%%i]!
      ) else (
      set /a paramCount+=1
      set param[!paramCount!]=!argv[%%i]!
      )
    )
  ::
  echo switchCount=%switchCount%
  echo paramCount=%paramCount%
  for /l %%i in (1,1,%switchCount%) do (
    echo swithch[%%i]=!switch[%%i]!)
  for /l %%i in (1,1,%paramCount%) do (
    echo param[%%i]=!param[%%i]!)
  endlocal & goto :EOF

The output might be e.g.
  C:\_D\TEST>cmdfaq a /b /c "Hello World" /?
  argc=5
  argv[1]=a
  argv[2]=/b
  argv[3]=/c
  argv[4]=Hello World
  argv[5]=/?
  switchCount=3
  paramCount=2
  swithch[1]=/b
  swithch[2]=/c
  swithch[3]=/?
  param[1]=a
  param[2]=Hello World

Take three:
Let's drop the switch handing but add how to get the parameters so that also the poison characters can be handled. At the same time the code is much streamlined. Note the trick to use the call. Also, no shift.
  @echo off & setlocal enableextensions disabledelayedexpansion
  set n=0
  for %%a in (%*) do (set /a n+=1)&(call set p[%%n%%]=%%a)
  ::
  echo n=%n%
  for /L %%i in (1,1,9) do if defined p[%%i] call echo p[%%i]=%%p[%%i]%%
  for /L %%i in (2,1,9) do if defined p[%%i] call set rest_=%%rest_%% %%p[%%i]%%
  if defined rest_ set rest_=%rest_:~1%
  echo rest_=%rest_%
  endlocal & goto :EOF


An example of the output
  C:\_D\TEST>cmdfaq 1 2 3 "Hello World" "a & b"
  n=5
  p[1]=1
  p[2]=2
  p[3]=3
  p[4]="Hello World"
  p[5]="a & b"
  rest_=2 3 "Hello World" "a & b"

Take four:
A Visual Basic Script (VBScript) aided command line script giving the number of parameters
  @echo off & setlocal enableextensions
  set vbs_=%temp%\tmp$$$.vbs
  > "%vbs_%" echo Set argv = WScript.Arguments
  >>"%vbs_%" echo WScript.Echo argv.count
  for /f %%a in ('cscript //nologo "%vbs_%" %*') do (
    set argc=%%a)
  for %%f in ("%vbs_%") do if exist %%f del %%f
  echo argc=%argc%
  endlocal & goto :EOF

An example of the output
  C:\_D\TEST>cmdfaq 1 2 3 "Hello World" "a & b"
  argc=5

Let's also get the value of the last parameter
  @echo off & setlocal enableextensions
  set vbs_=%temp%\tmp$$$.vbs
  > "%vbs_%" echo Set argv = WScript.Arguments
  >>"%vbs_%" echo If argv.count = 0 Then
  >>"%vbs_%" echo   WScript.Echo argv.count, "None"
  >>"%vbs_%" echo   Else
  >>"%vbs_%" echo   WScript.Echo argv.count, argv(argv.count-1)
  >>"%vbs_%" echo End If
  for /f "tokens=1,*" %%a in ('cscript //nologo "%vbs_%" %*') do (
    set argc=%%a
    set argLast="%%b")
  for %%f in ("%vbs_%") do if exist %%f del %%f
  echo argc=%argc%
  echo argLast=%argLast%
  endlocal & goto :EOF

An example of the output
  C:\_D\TEST>cmdfaq 1 2 3 "Hello World" "a & b"
  argc=5
  argLast="a & b"

Take five:
Consider a related, perhaps somewhat simpler question about parsing unordered command-line switches as quoted from the alt.msdos.batch.nt newsgroup Mon, 5 Dec 2011 20:55:14. "For instance, say I wanted to allow switches such as /f (force) and /s (silent) and others for controlling the actions within the batch itself, without requiring them to be in a strict order so that they appear within %1, %2, etc. Is there a way to parse them?"

Perhaps the solution easiest to understand is the following
  @echo off & setlocal enableextensions
  set par1=%~1
  set par2=%~2
  set par3=%~3
  for %%p in (%par1% %par2% %par3%) do (if /i "%%p"=="/f" set force=true)
  for %%p in (%par1% %par2% %par3%) do (if /i "%%p"=="/s" set silent=true)
  for %%p in (%par1% %par2% %par3%) do (if /i "%%p"=="/h" set helpme=true)
  if defined helpme (call :usage & goto :EOF)
  rem ... whatever the script is actually supposed to do ...
  endlocal & goto :EOF
  ::
  :usage
  echo An explanation of the usage
  goto :EOF

Although it is an aside, consider a subtle catch. The following will not work as maybe expected:
  @echo off & setlocal enableextensions
  set par1=%~1
  set par2=%~2
  set par3=%~3
  for %%p in (%par1% %par2% %par3%) do (if /i "%%p"=="/f" set force=true)
  for %%p in (%par1% %par2% %par3%) do (if /i "%%p"=="/s" set silent=true)
  for %%p in (%par1% %par2% %par3%) do (if /i "%%p"=="/?" set helpme=true)
  if defined helpme (call :usage & goto :EOF)
  rem ... whatever the script is actually supposed to do ...
  endlocal & goto :EOF
  ::
  :usage
  echo An explanation of the usage
  goto :EOF
but this (which also is more logical) will work (gives the help when required):
  @echo off & setlocal enableextensions
  if "%~1"=="/?" (call :usage & goto :EOF)
  set par1=%~1
  set par2=%~2
  for %%p in (%par1% %par2%) do (if /i "%%p"=="/f" set force=true)
  for %%p in (%par1% %par2%) do (if /i "%%p"=="/s" set silent=true)
  rem ... whatever the script is actually supposed to do ...
  endlocal & goto :EOF
  ::
  :usage
  echo An explanation of the usage
  goto :EOF

A perhaps more elegant formulation of a solution is the following. Also consider that in a script one would also wish to ensure an appropriate error message if a non-intended switch were given to the script. Furthermore, the code below has some debugging in it (echoes the status of the swicthes and shows when the main body has been reached).
  @echo off & setlocal enableextensions
  echo.%*|find /i "/?" >nul && set "helpme=true" || set "helpme="
  echo.%*|find /i "/f" >nul && set "force=true"  || set "force="
  echo.%*|find /i "/s" >nul && set "silent=true" || set "silent="
  ::
  :: Debug
  echo force=%force%
  echo silent=%silent%
  echo helpme=%helpme%
  ::
  echo.%*|find "/">nul^
    && if not defined force if not defined silent if not defined helpme (
      call :error & goto :EOF)
  if defined helpme (call :usage & goto :EOF)
  ::
  echo ... whatever the script is actually supposed to do ...
  ::
  endlocal & goto :EOF
  ::
  :: Error subroutine
  :error
  echo Error: No valid switch on the command line
  call :usage
  goto :EOF
  ::
  :: Help subroutine
  :usage
  echo An explanation of the usage
  goto :EOF
Note the echo.%*|find /i "/?" because the period is essential. The reason is that an echo /? will not output /? but the ECHO command help. More on echo catches in the item link list at the end of item #17.

Take six:
A case where the switches can be in any order. And where the number of the switches given to the script can vary.
  @echo off & setlocal enableextensions
  if "%~1"=="?" (call :ProgramTitle & echo.& call :ProgramHelp & goto :EOF)
  if "%~1"=="/?" (call :ProgramTitle & echo.& call :ProgramHelp & goto :EOF)
  ::
  :: Based on parsing the parameters, set the relevant environment variables

  set includeDate_=
  set includeExes_=
  set extractFiles_=
  set includePhotos_=true
  set onlyPhotos_=
  set par1_=%~1
  set par2_=%~2
  set par3_=%~3
  set par4_=%~4
  set par5_=%~5
  for %%p in ("%par1_%" "%par2_%" "%par3_%" "%par4_%" "%par5_%") do (
    if /i "%%~p"=="/d" set includeDate_=true
    if /i "%%~p"=="+x" set includeExes_=true
    if /i "%%~p"=="-j" set includePhotos_=
    if /i "%%~p"=="+j" set onlyPhotos_=true
    if /i "%%~p"=="/e" set extractFiles_=true)
  ::
  :: Check the validity of the parameters, i.e. that all fall within the allowed set
  :: Note that a no-switches case also is valid

  set param_ok_=true
  for %%p in ("%par1_%" "%par2_%" "%par3_%" "%par4_%" "%par5_%") do (
    if not "%%~p"=="" if /i not "%%~p"=="/d" if /i not "%%~p"=="+x" if /i not "%%~p"=="-j" if /i not "%%~p"=="+j" if /i not "%%~p"=="/e" set param_ok_=false
    )
  if /i "%param_ok_%"=="false" (
    call :ProgramTitle
    echo.
    echo Error in a parameter
    echo.
    call :ProgramHelp
    goto :EOF)
  ::
  call :ProgramTitle
  echo.
  call :ShowTheSetEnv
  endlocal & goto :EOF
 
  :ProgramTitle
  echo +-----------------------------------------------------+
  echo : A script to build or extract from a certain zipfile :
  echo : This demonstration only includes the parsing phase  :
  echo +-----------------------------------------------------+
  goto :EOF
 
  :ProgramHelp
  echo Usage: %~f0 [/D] [/E] [+X] [-J] [+J]
  echo   /D Include date into the zip file name
  echo   /E Extract instead of building the zipfile
  echo   +X Include also *.exe files
  echo   -J Exclude *.JPG files
  echo   +J Only include *.JPG files
  goto :EOF
 
  :ShowTheSetEnv
  echo includeDate_ =%includeDate_%
  echo includeExes_ =%includeExes_%
  echo extractFiles_ =%extractFiles_%
  echo includePhotos_=%includePhotos_%
  echo onlyPhotos_ =%onlyPhotos_%
  goto :EOF

You might find also item #176 of interest.

References/Comments: (If a Google message link fails try the links within the brackets.)
  Google Groups Dec 22 2003, 2:46 pm [M]
  Google Groups May 9 2004, 5:41 pm [M]
  Google Groups May 9 2004, 10:18 pm [M]
  Google Groups Apr 13 2006, 8:10 pm [M]
  Google Groups Mar 2 2008, 6:52 am [M]
  Google Groups Dec 5 2011 10:26 pm, etc
  Google Groups Dec 12 2011 8:58 pm