Windows Bat文件可选参数parsing

我需要我的bat文件来接受多个可选的命名参数。

mycmd.bat man1 man2 -username alice -otheroption 

例如,我的命令有两个必须的参数和两个可选参数(-username),它们的参数值为alice,并且–otheroption:

我希望能够将这些值抽取到variables中。

只是打电话给任何已经解决了这个问题的人。 这些蝙蝠文件是一个痛苦的人。

尽pipe我倾向于同意@AlekDavis的评论 ,但在NT shell中还是有几种方法来做到这一点。

我将利用SHIFT命令和IF条件分支的方法,像这样…

 @ECHO OFF SET man1=%1 SET man2=%2 SHIFT & SHIFT :loop IF NOT "%1"=="" ( IF "%1"=="-username" ( SET user=%2 SHIFT ) IF "%1"=="-otheroption" ( SET other=%2 SHIFT ) SHIFT GOTO :loop ) ECHO Man1 = %man1% ECHO Man2 = %man2% ECHO Username = %user% ECHO Other option = %other% REM ...do stuff here... :theend 

选定的答案有效,但可以使用一些改进。

  • 这些选项可能应该初始化为默认值。
  • 保存%0以及所需的参数%1和%2将会很好。
  • 对每个选项都有一个IF块是一件很痛苦的事情,特别是随着期权数量的增长。
  • 有一个简单而简洁的方法来快速定义一个地方的所有选项和默认值将是很好的。
  • 支持用作标志的独立选项(在选项后没有值)是很好的。
  • 我们不知道是否引用了一个参数。 我们也不知道是否使用转义字符传递了参数值。 最好使用%〜1来访问一个arg,并把这个赋值放在引号内。 那么批处理可以依靠封闭引号的缺失,但特殊字符仍然通常是安全的,没有逃脱。 (这不是防弹的,但它可以处理大多数情况)

我的解决scheme依赖于创build一个OPTIONSvariables来定义所有的选项及其默认值。 OPTIONS也用于testing提供的选项是否有效。 只需将选项值存储在与选项相同的variables中即可保存大量的代码。 无论定义了多less个选项,代码量都是恒定的; 只有选项定义必须改变。

编辑 – 此外,如果强制位置参数的数量发生变化,则必须更改:循环代码。 例如,通常所有参数都被命名,在这种情况下,您想要parsing从位置1开始而不是3的参数。因此在:loop中,所有3变为1,而4变为2。

 @echo off setlocal enableDelayedExpansion :: Define the option names along with default values, using a <space> :: delimiter between options. I'm using some generic option names, but :: normally each option would have a meaningful name. :: :: Each option has the format -name:[default] :: :: The option names are NOT case sensitive. :: :: Options that have a default value expect the subsequent command line :: argument to contain the value. If the option is not provided then the :: option is set to the default. If the default contains spaces, contains :: special characters, or starts with a colon, then it should be enclosed :: within double quotes. The default can be undefined by specifying the :: default as empty quotes "". :: NOTE - defaults cannot contain * or ? with this solution. :: :: Options that are specified without any default value are simply flags :: that are either defined or undefined. All flags start out undefined by :: default and become defined if the option is supplied. :: :: The order of the definitions is not important. :: set "options=-username:/ -option2:"" -option3:"three word default" -flag1: -flag2:" :: Set the default option values for %%O in (%options%) do for /f "tokens=1,* delims=:" %%A in ("%%O") do set "%%A=%%~B" :loop :: Validate and store the options, one at a time, using a loop. :: Options start at arg 3 in this example. Each SHIFT is done starting at :: the first option so required args are preserved. :: if not "%~3"=="" ( set "test=!options:*%~3:=! " if "!test!"=="!options! " ( rem No substitution was made so this is an invalid option. rem Error handling goes here. rem I will simply echo an error message. echo Error: Invalid option %~3 ) else if "!test:~0,1!"==" " ( rem Set the flag option using the option name. rem The value doesn't matter, it just needs to be defined. set "%~3=1" ) else ( rem Set the option value using the option as the name. rem and the next arg as the value set "%~3=%~4" shift /3 ) shift /3 goto :loop ) :: Now all supplied options are stored in variables whose names are the :: option names. Missing options have the default value, or are undefined if :: there is no default. :: The required args are still available in %1 and %2 (and %0 is also preserved) :: For this example I will simply echo all the option values, :: assuming any variable starting with - is an option. :: set - :: To get the value of a single parameter, just remember to include the `-` echo The value of -username is: !-username! 

真的没有太多的代码。 上面的大部分代码都是注释。 这是完全相同的代码,没有评论。

 @echo off setlocal enableDelayedExpansion set "options=-username:/ -option2:"" -option3:"three word default" -flag1: -flag2:" for %%O in (%options%) do for /f "tokens=1,* delims=:" %%A in ("%%O") do set "%%A=%%~B" :loop if not "%~3"=="" ( set "test=!options:*%~3:=! " if "!test!"=="!options! " ( echo Error: Invalid option %~3 ) else if "!test:~0,1!"==" " ( set "%~3=1" ) else ( set "%~3=%~4" shift /3 ) shift /3 goto :loop ) set - :: To get the value of a single parameter, just remember to include the `-` echo The value of -username is: !-username! 

此解决scheme在Windows批处理中提供Unix样式参数。 这不是Windows的规范 – 批处理通常具有所需参数前面的选项,而选项前缀为/

此解决scheme中使用的技术很容易适应Windows风格的选项。

  • parsing循环总是在%1处查找一个选项,并继续,直到arg 1不以/
  • 请注意,如果名称以/开头,SET分配必须包含在引号内。
    SET /VAR=VALUE失败
    SET "/VAR=VALUE"起作用。 无论如何,我已经在我的解决scheme中这样做了。
  • 标准的Windows风格排除了以/开头的所需参数值的可能性。 这个限制可以通过使用隐式定义的//选项来消除,该选项用作退出选项parsing循环的信号。 没有什么会被存储为// “选项”。

更新2015-12-28:支持! 在选项值

在上面的代码中,每个参数都是扩展的,而延迟扩展是启用的,这意味着! 很有可能被剥离,否则就像!var! 被扩大。 另外,如果^也可以被剥离! 存在。 对未注释的代码进行如下小的修改将删除这样的限制!^保存在选项值中。

 @echo off setlocal enableDelayedExpansion set "options=-username:/ -option2:"" -option3:"three word default" -flag1: -flag2:" for %%O in (%options%) do for /f "tokens=1,* delims=:" %%A in ("%%O") do set "%%A=%%~B" :loop if not "%~3"=="" ( set "test=!options:*%~3:=! " if "!test!"=="!options! " ( echo Error: Invalid option %~3 ) else if "!test:~0,1!"==" " ( set "%~3=1" ) else ( setlocal disableDelayedExpansion set "val=%~4" call :escapeVal setlocal enableDelayedExpansion for /f delims^=^ eol^= %%A in ("!val!") do endlocal&endlocal&set "%~3=%%A" ! shift /3 ) shift /3 goto :loop ) goto :endArgs :escapeVal set "val=%val:^=^^%" set "val=%val:!=^!%" exit /b :endArgs set - :: To get the value of a single parameter, just remember to include the `-` echo The value of -username is: !-username! 

如果你想使用可选的参数,但没有命名的参数,那么这种方法为我工作。 我认为这是更容易遵循的代码。

 REM Get argument values. If not specified, use default values. IF "%1"=="" ( SET "DatabaseServer=localhost" ) ELSE ( SET "DatabaseServer=%1" ) IF "%2"=="" ( SET "DatabaseName=MyDatabase" ) ELSE ( SET "DatabaseName=%2" ) REM Do work ECHO Database Server = %DatabaseServer% ECHO Database Name = %DatabaseName% 

这里是参数parsing器。 您可以混合任何string参数(保持不变)或转义选项(单个或选项/值对)。 要testing它取消最后2条语句的注释并运行如下:

getargs anystr1 anystr2 / test $ 1 / test $ 2 = 123 / test $ 3 str anystr3

Escape char被定义为“ SEP = /”,如果需要的话重新定义。

 @echo off REM Command line argument parser. Format (both "=" and "space" separators are supported): REM anystring1 anystring2 /param1 /param2=value2 /param3 value3 [...] anystring3 anystring4 REM Returns enviroment variables as: REM param1=1 REM param2=value2 REM param3=value3 REM Leading and traling strings are preserved as %1, %2, %3 ... %9 parameters REM but maximum total number of strings is 9 and max number of leading strings is 8 REM Number of parameters is not limited! set _CNT_=1 set _SEP_=/ :PARSE if %_CNT_%==1 set _PARAM1_=%1 & set _PARAM2_=%2 if %_CNT_%==2 set _PARAM1_=%2 & set _PARAM2_=%3 if %_CNT_%==3 set _PARAM1_=%3 & set _PARAM2_=%4 if %_CNT_%==4 set _PARAM1_=%4 & set _PARAM2_=%5 if %_CNT_%==5 set _PARAM1_=%5 & set _PARAM2_=%6 if %_CNT_%==6 set _PARAM1_=%6 & set _PARAM2_=%7 if %_CNT_%==7 set _PARAM1_=%7 & set _PARAM2_=%8 if %_CNT_%==8 set _PARAM1_=%8 & set _PARAM2_=%9 if "%_PARAM2_%"=="" set _PARAM2_=1 if "%_PARAM1_:~0,1%"=="%_SEP_%" ( if "%_PARAM2_:~0,1%"=="%_SEP_%" ( set %_PARAM1_:~1,-1%=1 shift /%_CNT_% ) else ( set %_PARAM1_:~1,-1%=%_PARAM2_% shift /%_CNT_% shift /%_CNT_% ) ) else ( set /a _CNT_+=1 ) if /i %_CNT_% LSS 9 goto :PARSE set _PARAM1_= set _PARAM2_= set _CNT_= rem getargs anystr1 anystr2 /test$1 /test$2=123 /test$3 str anystr3 rem set | find "test$" rem echo %1 %2 %3 %4 %5 %6 %7 %8 %9 :EXIT