代码之家  ›  专栏  ›  技术社区  ›  chickeninabiscuit

Windows BAT文件可选参数分析

  •  68
  • chickeninabiscuit  · 技术社区  · 15 年前

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

    mycmd.bat man1 man2 -username alice -otheroption
    

    例如,我的命令有两个强制参数和两个可选参数(-username),参数值为alice和-otheroption:

    我希望能够将这些值提取到变量中。

    只要打电话给任何已经解决了这个问题的人。伙计,这些蝙蝠档案真是痛苦。

    5 回复  |  直到 7 年前
        1
  •  98
  •   Community Mohan Dere    9 年前

    尽管我倾向于同意 @AlekDavis' comment 但是,在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
    
        2
  •  56
  •   dbenham    10 年前

    选定的答案是有效的,但它可能需要一些改进。

    • 选项可能应初始化为默认值。
    • 最好保留%0以及所需的参数%1和%2。
    • 每个选项都有一个if块会让人很痛苦,特别是随着选项数量的增加。
    • 最好有一种简单而简洁的方法在一个地方快速定义所有选项和默认值。
    • 最好支持作为标志的独立选项(选项后面没有值)。
    • 我们不知道arg是否用引号括起来。我们也不知道是否使用转义字符传递了arg值。最好使用%~1访问一个arg,并用引号括起来。然后,批处理可以依赖于没有括起来的引号,但是特殊字符在没有转义的情况下仍然是安全的。(这不是防弹的,但可以处理大多数情况)

    我的解决方案依赖于创建一个选项变量,该变量定义所有选项及其默认值。选项还用于测试提供的选项是否有效。只需将选项值存储在与选项同名的变量中,就可以节省大量代码。不管定义了多少个选项,代码量都是常量;只有选项定义必须更改。

    编辑 -此外,如果强制位置参数的数目发生更改,则:循环代码必须更改。例如,通常情况下,所有参数都是命名的,在这种情况下,您希望解析从位置1开始而不是从3开始的参数。所以在:循环中,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 /VAR=VALUE 失败
      SET "/VAR=VALUE" 作品。无论如何,我已经在我的解决方案中这样做了。
    • 标准的windows样式排除了从 / . 这种限制可以通过使用隐式定义的 // 作为退出选项解析循环的信号的选项。不会为 / / “期权”。


    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!
    
        3
  •  17
  •   charlie.mott    12 年前

    如果您想使用可选参数,但不使用命名参数,那么这种方法对我很有效。我认为这是更容易遵循的代码。

    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%
    
        4
  •  1
  •   Equation Solver    8 年前

    一旦我编写了一个程序来处理批处理文件中的short(-h)、long(--help)和non-option参数。 这种技术包括:

    • 后面跟着选项参数的非选项参数。

    • 没有参数的选项的shift运算符,如“--help”。

    • 需要参数的那些选项的两个时间移位运算符。

    • 循环通过一个标签来处理所有命令行参数。

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

    • 为用户Guidness编写了帮助函数

    这是我的密码。

    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
    
        5
  •  1
  •   MLavoie    7 年前

    这里是参数解析器。您可以混合任何字符串参数(保持不变)或转义选项(单个或选项/值对)。要测试它,请取消对最后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
    
    推荐文章