【问题标题】:Windows Bat file optional argument parsingWindows Bat 文件可选参数解析
【发布时间】:2011-04-27 19:01:50
【问题描述】:

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

mycmd.bat man1 man2 -username alice -otheroption

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

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

只需向已经解决此问题的任何人打个电话。伙计,这些 bat 文件很痛苦。

【问题讨论】:

  • 有什么理由必须在 BAT 文件中执行此操作而不是在 VBScript 中说明?可能有一种方法可以在 BAT 文件中执行此操作,但坚持 BAT 文件方法,您“您正在进入一个痛苦的世界,儿子”。 :-)
  • 更好的是使用 PowerShell。它具有非常先进的内置参数管理。
  • @AlekDavis,你可能是对的,但我非常害怕 VBScript。如果我发现自己不得不使用 VBScript,我想我已经陷入了痛苦的世界。我很乐意为自己编写一个批处理文件来自动化某些事情,然后也许如果我想让它对人们更有用,那么我会添加一些参数。通常其中一些是可选的。自从我离开银行业以来,我从未想过,或者有人建议“你需要一些 VBScript”。
  • @chickeninabiscuit:链接不再有效。 Wayback 机器版本可以。我不知道我是否编辑了你的评论,如果它会改变它看起来像我做的工作,我只是把它粘贴在这里:http://web.archive.org/web/20090403050231/http://www.pcguide.com/vb/showthread.php?t=52323

标签: batch-file


【解决方案1】:

虽然我倾向于同意@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

【讨论】:

  • SHIFT /2 从 arg 2 开始移动。它不会移动两次。不应该换成SHIFT & SHIFT吗?
  • 好的 - 我明白为什么代码通常可以工作 - 循环中的 SHIFT 最终将选项移动到 arg 1 并且没有造成任何伤害(通常)。但是如果 arg 1 值恰好与选项名称之一匹配,则初始 SHIFT /2 绝对是一个错误,它将破坏结果。尝试运行mycmd.bat -username anyvalue -username myName -otheroption othervalue。结果应该是 user=myName, other=othervalue;但是代码给出了用户=-用户名,其他=其他值。该错误已通过将SHIFT /2 替换为SHIFT & SHIFT 来修复。
  • @Christian - ewall 关于; 的说法不正确 - 它不能用来代替&
  • 这是一个很好的开始,但我发现了 3 个问题。首先:a) 在第 10 行之后,%1%2 已被“使用”。 b) 第 11 行中的单个 SHIFT%2 的值向下移动 1 个位置,然后(再次)作为 %1 可用,%3 的“未使用”值作为 %2 可用。 c) 第 13 行中的 IF%2 中看到这个已经“使用”的值,现在位于 %1 中。 d) 如果第 10 行中的“用户名”看起来像 "-otheroption",它将再次使用,other 将设置为 %2 中的新值,这可能是下一个选项的名称。 --->
  • 第二个问题:在 OP 问题中,-otheroption 后面没有值,因此它可能意味着 -Flag 类型选项,不应使用第二个参数。第三:如果参数%1 没有被内部IF 语句处理,它会被忽略。该代码可能应该显示一条消息,表明它不是一个有效的选项。这可以通过将所需的SHIFTGOTO :loop 语句添加到内部IF 语句中,或通过添加ELSE 语句来完成。
【解决方案2】:

所选答案有效,但可能需要一些改进。

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

我的解决方案依赖于创建一个定义所有选项及其默认值的 OPTIONS 变量。 OPTIONS 还用于测试提供的选项是否有效。通过简单地将选项值存储在与选项名称相同的变量中,可以节省大量代码。无论定义了多少选项,代码量都是恒定的;只有 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!

确实没有那么多代码。上面的大部分代码都是cmets。这是完全相同的代码,但没有 cmets。

@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 更新:在选项值中支持 !

在上面的代码中,每个参数都在启用延迟扩展的情况下进行扩展,这意味着! 很可能被剥离,或者!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%
  • 非常优雅的解决方案。如果您考虑使用它,请注意所提供的代码将在参数 3 处开始解析。在一般情况下,这不是您想要的。用“1”替换“3”来解决这个问题。 @dbenham,如果您能在原始帖子中更明显地说明这一点,那就太好了,我可能不是唯一一个一开始感到困惑的人。
  • @ocroquette - 好点。鉴于我必须回答具体问题,我没有太多选择。但是当强制位置参数的数量发生变化时,确实必须修改代码。我将尝试编辑我的答案以使其清楚。
  • 我喜欢这个。感谢您的解决方案,对于某些 IDE 脚本,它在 2019 年仍然可以正常工作!
【解决方案3】:

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

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"=="" 将失败。只需使用任何其他非特殊符号,例如 ._=[]# 等
  • 我认为这行不通,除非用户知道使用 "" 代替参数。否则,我怎么能设置数据库名称但将数据库服务器留空?
  • 您如何看待另一个答案来实现这一目标? @SeanLong
  • @SeanLong 对,所以用户必须服从命令。因此,您应该将最需要的参数放在首位。
  • 这对我来说绝对是最简单的选择。感谢发帖:)
【解决方案4】:

动态变量创建

优点

  • 适用于 >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_D%


::*********************************************************
:: 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,使其也可用于其他脚本。
【解决方案5】:

一旦我编写了一个程序来处理批处理文件中的短 (-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

【讨论】:

    【解决方案6】:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2022-07-24
      • 2010-11-06
      • 2011-09-22
      • 2014-11-21
      • 2014-10-28
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多