如何在Windows批处理脚本中将可选的命令行参数值作为字符串获取?

3

如果有一个可选的端口参数,其中端口号的长度可能会变化,我该如何从批处理脚本的命令行参数中获取端口号?

示例:

foo.bat --foo bar --port 80 --bar foo

应该输出:

80

我试图使用substring方法,但只做到了这个程度。

set CMD_LINE_ARGS=%*
@rem Remove all chars and port arg
set PORT_ARG_REMOVED=%CMD_LINE_ARGS:*-port =%
@rem Obviously, this is where I fail to remove trailing chars
set PORT_NUM=%PORT_ARG_REMOVED: *=%
echo %PORT_NUM%

编辑

我选择的答案是因为它符合我的特定用例,我将所有参数都传递给了我包装的命令。而且,我只需要特定可选参数的值。不需要循环。

这里有一些非常好的答案来处理通用的可选参数解析。所以,请随意给大家点赞。

4个回答

3

看起来您真的很希望看到子字符串的工作结果,这里是按照您意图编写和结构化的脚本。

@Set "CMD_LINE_ARGS=%*"
@Rem Remove all chars and port arg
@Set "PORT_ARG_REMOVED=%CMD_LINE_ARGS:*-port =%"
@Rem Remove trailing chars
@Set "PORT_NUM=%PORT_ARG_REMOVED: ="&:"%"
@Echo %PORT_NUM%
@Pause

啊!是的。但是,你能解释一下第5行的语法吗?它看起来很奇怪。好像你甚至有一个不均匀的双引号数,但是它却可以工作。 - kevinmm
更确切地说,我看到你正在寻找空格,我猜这就是为什么你需要引用整个设置参数的原因。然后,通过="将其替换为空。但是,结尾的&:对我来说很迷惑。 - kevinmm
实际上,它将空格替换为"&:",因此在变量扩展后整行变成了@Set "PORT_NUM=80"&:""&允许在同一行上连接另一个命令,剩下的:""只是一个跳转标签,所以被忽略了... - aschipfl
如果可选参数的值可以模仿参数的名称,则此技术可能会失败。例如,foo.bat --foo --port --port 80 --bar foo 将产生 port=--port 的结果。 - dbenham
OP已经暗示他们确信输入参数将匹配脚本所需的格式,并且满足他们的要求,他们对此感到满意。 - Compo

2

您可以使用%1%2等来表示单独的命令行参数。在您的情况下,--port将是%3,其值将是%4

幸运的是,还有一个shift命令,它会将所有参数都向左移动,2变成1,3变成2等等。

这意味着您可以在循环中“抓取”所有命令行参数。您不断地进行移位,并且当您遇到--port时,您就知道下一个参数将是端口号,您可以将其存储在适当的变量中(使用set)以备后用。

如果您按照这种方式实现,您可以实现一堆可选参数,而且顺序也不重要。

因此,在代码中,您的'foo.bat'可能如下所示:

@echo off
:: No parameters given? Then run ourselves with the defaults from the question.
if "%1"=="" goto none

:: Actual reading of parameters starts here.

:loop
if "%1"=="--port" set port=%2
:: Of course you need the other ifs only if you're interested in these parameters
if "%1"=="--foo" set foo=%2
if "%1"=="--bar" set bar=%2

:: Shift all parameters, except %0. Do it twice to skip the parameter and its value.
shift /1
shift /1

:: As long as there are more, keep looping. You can even parse more than 10 parameters this way!
if not "%1"=="" goto loop

:: Output what we've found
echo The specified port is %port%
echo Foo is set to %foo%
echo The bar is at %bar%
pause

exit /b

:: If no parameters are given, call ourselves with example parameters.
:none
call %0 --foo bar --port 80 --bar foo

这是一种简化版本,没有仅显示端口号的演示废话。我认为这是您当前代码的替代方案。

@echo off
:loop
if "%1"=="--port" set port=%2
shift /1
shift /1
if not "%1"=="" goto loop
echo %port%

我同意这个方法是可行的。但是,它似乎要做很多工作,只是为了避免我认为在批处理中可以工作的东西。我一定会考虑这个作为一个答案。 - kevinmm
你可以使用稍微不同的方法将字符串分割并用循环迭代,但效果基本相同。也有获取子字符串的方法,但它们是根据字符计数工作的。也有字符串替换功能,但我不认为在这里真的有帮助。如果你为了演示而删除所有的开销,那么只需要5行代码就可以获得端口号。 - GolezTrol
需要循环的代码有9行(如果“if”语句只有1行,则可以争辩为7行),而没有循环的代码只有3行。你可以争辩我的方法中有4行,但我必须在两种方法中都保留命令行参数。只是这么说... - kevinmm
哈哈,我不是在争论代码行数。但是...以前我不需要检查参数,现在我需要了。而且,如果需要,我需要一个跳转标签。所以,这个7并没有被输出。 - kevinmm
我会使用%~1而不是%1来正确处理带引号的参数... - aschipfl
显示剩余2条评论

2

一种更简单和明显的方法,可以获取所有参数,无论有多少个:

@echo off
setlocal EnableDelayedExpansion

set "arg="
for %%a in (%*) do (
   if not defined arg (
      set "arg=%%a"
   ) else (
      set "!arg:~2!=%%a"
      set "arg="
   )
)

echo foo = %foo%
echo port = %port%
echo bar = %bar%

如果您喜欢简短的代码,则下面的版本可以用更少的行数完成相同的任务:
@echo off

set "args= %*"
set "args=%args: --=+%"
set "args=%args: ==%"
set args=%args:+=&set %

echo foo = %foo%
echo port = %port%
echo bar = %bar%

如果您想回顾一下较短版本的工作原理,只需删除@echo off行并执行即可。


我考虑过这个问题,但这意味着某人可以通过将它们作为命令行参数传递来覆盖现有的环境变量,从而影响您使用这些变量的脚本。这可能被认为是一种优势,也可能是一种漏洞。我认为这是后者。 - GolezTrol

0

我首先会使用子字符串替换删除--port部分,然后通过for /F循环获取值,就像这样:

set CMD_LINE_ARGS=%*
@rem Remove everything up to `--port` +  a space:
set "PORT_ARG_REMOVED=%CMD_LINE_ARGS:*--port =%"
@rem Extract first space- (or tab-)separated item:
for /F "tokens=1" %%A in ("%PORT_ARG_REMOVED%") do set "PORT_NUM=%%A"
echo/%PORT_NUM%

for /F 方法的最大优势在于,即使提供多个连续的SPACEs来分隔命令行参数,它也能正常工作。


如果你想让代码允许所有标准的令牌分隔符(包括SPACETAB,;=和字符编码为0xFF的字符),而不仅仅是SPACE,你可以将代码更改为以下内容:

set "FLAG="
@rem Use a standard `for` loop to walk through the arguments:
for %%A in (%*) do (
    @rem Capture the argument following the `--port` item:
    if defined FLAG if not defined PORT_NUM set "PORT_NUM=%%~A"
    @rem Use a `FLAG` variable to delay capturing argument by an iteration;
    @rem to match `--port` case-sensitively, remove the `/I` option:
    if /I "%%~A"=="--port" set "FLAG=#"
)
echo/%PORT_NUM%

其实我更喜欢这种方式,因为它不涉及字符串操作,因为参数列表是以cmd的方式解析的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接