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.