Windows Bat文件可选参数解析


84

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

mycmd.bat man1 man2 -username alice -otheroption

例如,我的命令具有2个必需参数,以及两个可选参数(-username),其参数值是alice和-otheroption:

我希望能够将这些值转换为变量。

只需向已解决此问题的任何人发出电话即可。伙计,这些蝙蝠锉是一件痛苦的事。


12
为什么必须在BAT文件中执行此操作而不在VBScript中说出任何原因?在BAT文件中可能有一种方法可以做到这一点,但是坚持使用BAT文件方法,您“您正在进入一个痛苦的世界,儿子”。:-)
Alek Davis

更好的是使用PowerShell。它具有非常先进的内置参数管理。
Bill_Stewart '16

1
@AlekDavis,您可能是对的,但我对VBScript的恐惧很高。如果我发现自己必须使用VBScript,我想我已经处在痛苦的世界中。我会很高兴为自己编写一个批处理文件以使某些操作自动化,然后也许如果我想使其对人们更有用,那么我将添加一些参数。通常,其中一些是可选的。自从我离开银行界以来,我从未想过,或者有人建议“您需要一些VBScript”。
icc97

@chickeninabiscuit:链接不再有效。Wayback机器版本可以。我不知道是否编辑您的评论,是否会将其更改为看起来像我所做的工作,而是将其粘贴到此处:http://web.archive.org/web/20090403050231/http://www.pcguide.com/vb/showthread.php?t=52323
布伦特·里滕豪斯

Answers:


112

尽管我倾向于同意@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

1
SHIFT /2从arg 2开始移位。它不移位两次。它不应该被替换SHIFT & SHIFT吗?
dbenham

2
好的-我知道为什么代码通常可以工作-循环中的SHIFT最终将选项移至arg 1且通常不会造成任何损害。但是,初始SHIFT / 2绝对是一个错误,如果arg 1值恰好与选项名称之一匹配,它将破坏结果。尝试跑步mycmd.bat -username anyvalue -username myName -otheroption othervalue。结果应为user = myName,other = othervalue;但是代码给出了user = -username,other = othervalue。该缺陷是由取代固定SHIFT /2SHIFT & SHIFT
dbenham

2
@Christian-ewall大约不正确;-不能代替使用&
dbenham

1
这是一个很好的开始,但我看到了3个问题。第一:一)线10后,%1%2已经被“使用”。b)第SHIFT11行中的单个对象将值%2向下移动1个位置,然后再次可用来作为%1,而的“未使用”值%3则可以作为来使用%2。c)在IF第13行认为,这已经是“已使用”值从%2它现在是%1。d)如果第10行中的“用户名”看起来像"-otheroption",它将被再次使用other并将被设置为中的新值%2,这可能是下一个选项的名称。--->
凯文·费根

2
第二个问题:在OP Question中,-otheroption后面没有值,因此它可能是-Flag类型选择,不应使用第二个参数。第三:如果%1内部IF语句未处理该参数,则将其忽略。该代码可能应该显示一条消息,指示它不是有效的选项。这可以通过在内部语句中添加requireSHIFT和语句,或通过添加语句来完成。GOTO :loopIFELSE
凯文·费根

74

选定的答案有效,但可能会有一些改进。

  • 这些选项可能应该初始化为默认值。
  • 保留%0以及所需的参数%1和%2会很好。
  • 为每个选项设置IF块变得很痛苦,尤其是随着选项数量的增加。
  • 如果有一种简单明了的方法来在一处快速定义所有选项和默认值,那就太好了。
  • 支持充当标志的独立选项会很好(该选项后没有值)。
  • 我们不知道arg是否包含在引号中。我们也不知道是否使用转义字符传递了arg值。最好使用%〜1访问arg并将赋值用引号引起来。然后,该批处理可以依靠不带引号引起来,但是特殊字符通常仍然是安全的,无需转义。(这不是防弹的,但可以处理大多数情况)

我的解决方案依赖于OPTIONS变量的创建,该变量定义所有选项及其默认值。选项还用于测试提供的选项是否有效。通过将选项值简单地存储在与选项相同的变量中,可以节省大量代码。无论定义了多少选项,代码量都是恒定的。仅选项定义必须更改。

编辑 -而且,如果强制位置参数的数量更改,则:loop代码必须更改。例如,通常所有参数都被命名,在这种情况下,您想解析从位置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!


此解决方案在Windows批处理中提供Unix样式参数。这不是Windows的规范-批处理通常在必需的参数之前具有选项,并且该选项带有前缀/

该解决方案中使用的技术很容易适应Windows风格的选项。

  • 解析循环始终在处寻找一个选项%1,并一直持续到arg 1不以开头/
  • 请注意,如果名称以开头,则SET分配必须用引号引起来/
    SET /VAR=VALUE失败了
    SET "/VAR=VALUE"。无论如何,我已经在解决方案中这样做了。
  • 标准Windows样式排除了以开头的第一个必需参数值的可能性/。通过使用隐式定义的//选项作为信号退出选项解析循环,可以消除此限制。//“选项”将不会存储任何内容。


2015年12月28日更新:支持!in选项值

在上面的代码中,在启用延迟扩展的同时扩展了每个参数,这意味着!最有可能剥离了该参数,否则将进行类似!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!

感谢您提供的解决方案非常优雅,我非常喜欢接受这一解决方案。您唯一想念的就是说如何在批处理脚本中使用命名的参数,例如echo%-username%
Roboblob 2013年

1
非常优雅的解决方案。如果考虑使用此功能,请注意,所提供的代码将开始在参数3处进行解析。这通常不是您想要的。用“ 1”代替“ 3”可解决此问题。@dbenham,如果您可以在原始帖子中让此内容变得更明显,那将很好,我可能不是唯一一开始感到困惑的人。
ocroquette '16

@ocroquette-好点。考虑到我必须回答特定的问题,我没有太多选择。但是,当强制位置参数的数量发生更改时,必须对代码进行修改。我将尝试编辑答案以使其清楚。
dbenham '16

我喜欢这个。谢谢您的解决方案,对于某些IDE脚本,它在2019年仍然可以正常使用!
罗伯·史密斯

18

如果要使用可选参数而不是命名参数,则此方法对我有用。我认为这是更容易遵循的代码。

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%

IF "%1"==""如果参数包含引号本身将失败。只需改用其他任何非特殊符号即可,例如._ = []#等
Fr0sT 2014年

2
除非用户知道使用“”代替参数,否则我认为这行不通。否则,如何设置数据库名称,但将数据库服务器留空?
肖恩·隆

您还如何看待另一个答案呢?@SeanLong
sehe 2015年

@SeanLong对,因此用户必须服从命令。因此,您应该将最需要的参数放在首位。
马丁·布劳恩

对于我来说,这绝对是最简单的选择。感谢您的张贴:)
史蒂夫·鲍曼

2

动态变量创建

在此处输入图片说明

优点

  • 适用于> 9个参数
  • 保持%1%2......%*机智
  • 同时适用于/arg-arg风格
  • 没有关于参数的先验知识
  • 实施与主程序分开

缺点

  • 旧的参数可能会泄漏到连续的运行中,因此可setlocal用于局部作用域或编写附带的:CLEAR-ARGS例程!
  • 没有别名还支持(如--force-f
  • 没有空""参数支持

用法

这是一个示例,以下参数如何与.bat变量相关:

>> testargs.bat /b 3 -c /d /e /f /g /h /i /j /k /bar 5 /foo "c:\"

echo %*        | /b 3 -c /d /e /f /g /h /i /j /k /bar 5 /foo "c:\"
echo %ARG_FOO% | c:\
echo %ARG_A%   |
echo %ARG_B%   | 3
echo %ARG_C%   | 1
echo %ARG_D%   | 1

实作

@echo off
setlocal

CALL :ARG-PARSER %*

::Print examples
echo: ALL: %*
echo: FOO: %ARG_FOO%
echo: A:   %ARG_A%
echo: B:   %ARG_B%
echo: C:   %ARG_C%
echo: D:   %ARG_C%


::*********************************************************
:: Parse commandline arguments into sane variables
:: See the following scenario as usage example:
:: >> thisfile.bat /a /b "c:\" /c /foo 5
:: >> CALL :ARG-PARSER %*
:: ARG_a=1
:: ARG_b=c:\
:: ARG_c=1
:: ARG_foo=5
::*********************************************************
:ARG-PARSER
    ::Loop until two consecutive empty args
    :loopargs
        IF "%~1%~2" EQU "" GOTO :EOF

        set "arg1=%~1" 
        set "arg2=%~2"
        shift

        ::Allow either / or -
        set "tst1=%arg1:-=/%"
        if "%arg1%" NEQ "" (
            set "tst1=%tst1:~0,1%"
        ) ELSE (
            set "tst1="
        )

        set "tst2=%arg2:-=/%"
        if "%arg2%" NEQ "" (
            set "tst2=%tst2:~0,1%"
        ) ELSE (
            set "tst2="
        )


        ::Capture assignments (eg. /foo bar)
        IF "%tst1%" EQU "/"  IF "%tst2%" NEQ "/" IF "%tst2%" NEQ "" (
            set "ARG_%arg1:~1%=%arg2%"
            GOTO loopargs
        )

        ::Capture flags (eg. /foo)
        IF "%tst1%" EQU "/" (
            set "ARG_%arg1:~1%=1"
            GOTO loopargs
        )
    goto loopargs
GOTO :EOF


您也可以将:ARG-PARSER例程的内容移至外部.bat文件,arg-parser.bat以使其也可用于其他脚本。
Simon Streicher

1

一旦我写了一个程序来处理批处理文件中的短(-h),长(--help)和非选项参数。该技术包括:

  • 非选项参数,后跟选项参数。

  • 那些没有参数的选项的移位运算符,例如“ --help”。

  • 两个时移运算符,用于需要参数的那些选项。

  • 遍历标签以处理所有命令行参数。

  • 退出脚本并停止处理不需要进一步操作的选项,例如“ --help”。

  • 编写帮助功能以指导用户

这是我的代码。

set BOARD=
set WORKSPACE=
set CFLAGS=
set LIB_INSTALL=true
set PREFIX=lib
set PROGRAM=install_boards

:initial
 set result=false
 if "%1" == "-h" set result=true
 if "%1" == "--help" set result=true
 if "%result%" == "true" (
 goto :usage
 )
 if "%1" == "-b" set result=true
 if "%1" == "--board" set result=true
 if "%result%" == "true" (
 goto :board_list
 )
 if "%1" == "-n" set result=true
 if "%1" == "--no-lib" set result=true
 if "%result%" == "true" (
 set LIB_INSTALL=false
 shift & goto :initial
 )
 if "%1" == "-c" set result=true
 if "%1" == "--cflag" set result=true
 if "%result%" == "true" (
 set CFLAGS=%2
 if not defined CFLAGS (
 echo %PROGRAM%: option requires an argument -- 'c'
 goto :try_usage
 )
 shift & shift & goto :initial
 )
 if "%1" == "-p" set result=true
 if "%1" == "--prefix" set result=true
 if "%result%" == "true" (
 set PREFIX=%2
 if not defined PREFIX (
 echo %PROGRAM%: option requires an argument -- 'p'
 goto :try_usage
 )
 shift & shift & goto :initial
 )

:: handle non-option arguments
set BOARD=%1
set WORKSPACE=%2

goto :eof


:: Help section

:usage
echo Usage: %PROGRAM% [OPTIONS]... BOARD... WORKSPACE
echo Install BOARD to WORKSPACE location.
echo WORKSPACE directory doesn't already exist!
echo.
echo Mandatory arguments to long options are mandatory for short options too.
echo   -h, --help                   display this help and exit
echo   -b, --boards                 inquire about available CS3 boards
echo   -c, --cflag=CFLAGS           making the CS3 BOARD libraries for CFLAGS
echo   -p. --prefix=PREFIX          install CS3 BOARD libraries in PREFIX
echo                                [lib]
echo   -n, --no-lib                 don't install CS3 BOARD libraries by default
goto :eof

:try_usage
echo Try '%PROGRAM% --help' for more information
goto :eof

1

这是参数解析器。您可以混合使用任何字符串参数(保持不变)或转义的选项(单个或选项/值对)。要测试它以取消注释最后2条语句并以以下方式运行:

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

转义字符定义为"_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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.