开发者问题收集

尝试修复嵌套延迟扩展问题时,由于在函数的 else 语句中添加 for 循环而导致无法解释的行为

2018-10-26
67

前言

我是批处理方面的新手,已经做了大约 2 周了。当我向函数添加特定的 for 循环时,它不遵循我认为的正常程序流规则。当删除 for 循环时,函数遵循我期望的程序流,但输出仍然不是我想要的。根本问题是延迟扩展变量内的延迟扩展变量。

我多次检查了括号,在靠近底部的完整代码中找不到任何不平衡的地方。从程序流的角度来看,这没有任何意义,添加 for 循环会影响 for 循环之前的语句。我唯一能想到的是,发生了一些奇怪的错误,代码根本没有执行完整的循环,而是在循环之后立即到达我的暂停语句。

我实际上想做的

我试图用 ; 来分隔一行字符串字符串中含有未知/可变数量的标记和其他可能的分隔符(如逗号),我想稍后再进行分隔。这无法通过批处理预制的 for 循环完成,FOR /F 只能执行行和/或设置标记数,而常规 for 循环同时用分号和逗号分隔。我想先用分号分隔,然后再用逗号分隔。为此,我尝试制作自己的简单分行 for 循环,并添加标签。我遇到了一个涉及变量扩展延迟的问题。此处的示例:

不是完整代码,但包含延迟扩展问题的代码

setlocal EnableDelayedExpansion
set "TestArgument=%~1"
set /a "CurrentIndex=0"
set /a "DelimIndex=0"
set /a "LoopCount=0"
:ForEach
IF "!TestArgument:~%CurrentIndex%,1!"=="" (goto :ForEachEnd & echo End of list)
echo testing semicolon
IF NOT "!TestArgument:~%CurrentIndex%,1!"==";" (SET /a CurrentIndex=%CurrentIndex%+1 & goto :ForEach & echo found non ;) ELSE (
    set /a "LengthToSearch=%CurrentIndex%-%DelimIndex%"
    SET "currentArg=!TestArgument:~%DelimIndex%,!LengthToSearch!!

我希望从函数的这个特定部分获得什么

示例输入:

set "ICMPv4RuleTest="Enable Echo Ping Request ICMPv4;Protocol,ICMPv4;Enabled,Yes;LocalIP,Any;RemoteIP,Any;Direction,In;Action,Allow""

示例所需输出:For 循环(上面的标签),其中每次迭代都采用一个以 分隔的字符串;因此第一次迭代将采用 Enable Echo Ping Request ICMPv4 值。上面的整个要点是获取输入并以某种方式对其进行子字符串化,以便每次迭代都以分号分隔

上面的代码,特别是最后一行导致了问题,因为(我认为)程序将其读为延迟扩展 !TestArgument:~%DelimIndex%,! (这将出错)然后 !!(什么都没有)只留下 LengthToSearch 作为原始字符串。我想要的是在子字符串操作之前扩展 LengthToSearch 值,然后扩展/运行/执行子字符串操作的扩展并将其设置为 CurrentArg(以 ; 分隔的单行字符串)。我无法将 % 符号与 LengthtoSearch 一起使用,因为它似乎与子字符串操作处于同一范围内,但是我可以将 % 符号与 DelimIndex 一起使用(因为这超出了范围?我不知道)。

我尝试过的方法

我尝试过子字符串行(最后一行)的变体,包括:

  • SET "currentArg=!TestArgument:~%DelimIndex%,%LengthToSearch%! 不打印任何内容。
  • SET "currentArg=!TestArgument:~%DelimIndex%,!!LengthToSearch!!! 打印 31(DelimIndex 的文字参数)。
  • SET "currentArg=%TestArgument:~%DelimIndex%,!!LengthToSearch!!% 不打印任何内容。
  • SET “currentArg=%TestArgument:~%DelimIndex%,!LengthToSearch!% 不打印任何内容。

我尝试过其他一些方法,但它们肯定不正确。我认为应该是

  • SET "currentArg=!TestArgument:~%DelimIndex%,!LengthToSearch!! .

方式,但这种方式存在上述问题。我在这里做了一些研究,发现其他人遇到了与我的字符串搜索和替换问题类似的问题,他们使用 FOR 循环将延迟扩展值插入到延迟扩展子字符串操作中作​​为 for 循环参数。我想我可以使用相同的方法,所以我尝试了这个(最后一行有很大的改动):

setlocal EnableDelayedExpansion
set "TestArgument=%~1"
set /a "CurrentIndex=0"
set /a "DelimIndex=0"
set /a "LoopCount=0"
:ForEach
IF "!TestArgument:~%CurrentIndex%,1!"=="" (goto :ForEachEnd & echo End of list)
echo testing semicolon
IF NOT "!TestArgument:~%CurrentIndex%,1!"==";" (SET /a CurrentIndex=%CurrentIndex%+1 & goto :ForEach & echo found non ;) ELSE (
    set /a "LengthToSearch=%CurrentIndex%-%DelimIndex%"
    FOR /F "tokens=*" %%A IN ("!LengthToSearch!") DO (SET "currentArg=!TestArgument:~%DelimIndex%,%%A!)

而这时事情变得非常奇怪。出于某种原因,添加 forloop 导致整个 ELSE 情况无法执行。当我运行我的代码时,

IF NOT "!TestArgument:~%CurrentIndex%,1!"==";"

仅“运行”一次(它并没有真正运行它应该运行的所有内容),然后循环实际上中断了。我并不意味着它会到达循环的末尾,它只是完全忽略了函数的其余部分。我已多次检查了完整代码中的括号:

函数的完整代码

:FirewallRuleTestFunc
echo got to function
setlocal EnableDelayedExpansion
set "TestArgument=%~1"
ECHO PARAM WAS "%~1"
echo TestArgument was "%TestArgument%"
set /a "CurrentIndex=0"
set /a "DelimIndex=0"
set /a "LoopCount=0"
echo trying foreach
:ForEach
echo got to foreach
IF "!TestArgument:~%CurrentIndex%,1!"=="" (goto :ForEachEnd & echo End of list)
echo testing semicolon
IF NOT "!TestArgument:~%CurrentIndex%,1!"==";" (SET /a CurrentIndex=%CurrentIndex%+1 & goto :ForEach & echo found non ;) ELSE (
    echo WHY IS THIS NOT BEING REACHED.
    echo CurrentIndex is %CurrentIndex%
    echo startposition was first: %DelimIndex%
    set /a "LengthToSearch=%CurrentIndex%-%DelimIndex%"
    echo length to search was: !LengthToSearch!
    echo length to search was: %LengthToSearch%
    echo startposition was: %DelimIndex%
    FOR /F "tokens=*" %%A IN ("!LengthToSearch!") DO (SET "currentArg=!TestArgument:~%DelimIndex%,%%A!)
    echo loop of ; Arg was: !currentArg!
    set /a DelimIndex=!CurrentIndex!  
    echo Delim index was: %DelimIndex%
    IF !LoopCount! NEQ 0 (
        echo afterfirstloop
        FOR /F "tokens=1,2 delims=," %%A IN ('netsh advfirewall firewall show rule name^="!RuleName!" ^| FIND "%%A"') DO (
            echo loop of , A was '%%A'
            SET "FoundArgument=%%B"
            SET "FirewallRuleExists=%%A"
            IF "!FirewallRuleExists!"=="" (echo TEST FAILED firewall rule with name "!RuleName!" NOT FOUND)
            IF "!FoundArgument!"=="Yes" (echo Test Passed) ELSE (echo Test Failed was "!FoundArgument!")
        )
    ) ELSE (SET "RuleName=!currentArg!" & set /a "LoopCount=%LoopCount%+1" & echo firstloop done)
    echo got past if else
)
:ForEachEnd
echo end of THING
endlocal
echo SOMETHING
goto:EOF

并且第二个 IF 的 ELSE 情况( IF NOT "!TestArgument:~%CurrentIndex%,1!"==";" )中的任何回显,以及除第二个 IF 之前的回显之外的任何其他回显都没有真正发生。这对我来说完全没有意义,我无法理解为什么它在添加 for 循环时会忽略程序流,而在注释掉 for 循环时程序流会正常工作。我想要做的就是让 for 循环按照我可以指定的分隔符循环一次。我唯一的猜测是发生了一些错误导致它直接进入 EOF。在我的函数被调用之后,有一个暂停,因为我的程序的其余部分应该是无关紧要的。我目前正在处理这一部分。函数的输入如下所示:

函数调用示例:

set "ICMPv4RuleTest="Enable Echo Ping Request ICMPv4;Protocol,ICMPv4;Enabled,Yes;LocalIP,Any;RemoteIP,Any;Direction,In;Action,Allow""
call :FirewallRuleTestFunc %ICMPv4RuleTest%

我非常沮丧,我只需要一些帮助。我会继续尝试各种方法,但这是我需要完成的程序的最后一部分,我只想获得一些稳定、有效和高效的东西,而不必做我以前做过的事情,使用行列和幻数通过令牌检查防火墙设置。(我正在测试一个脚本来配置一些设置,以便可以设置公司网络)。

2个回答

如果您想 在分号处 拆分字符串,则可以使用这种简单的方法:

@echo off
setlocal

set "ICMPv4RuleTest=Enable Echo Ping RequestICMPv4;Protocol,ICMPv4;Enabled,Yes;LocalIP,Any;RemoteIP,Any;Direction,In;Action,Allow"

echo Original string: "%ICMPv4RuleTest%"
echo/

echo Split string at semicolons:
for %%a in ("%ICMPv4RuleTest:;=" "%") do echo %%a

输出:

Original string: "Enable Echo Ping Request ICMPv4;Protocol,ICMPv4;Enabled,Yes;Lo
calIP,Any;RemoteIP,Any;Direction,In;Action,Allow"

Split string at semicolons:
"Enable Echo Ping Request ICMPv4"
"Protocol,ICMPv4"
"Enabled,Yes"
"LocalIP,Any"
"RemoteIP,Any"
"Direction,In"
"Action,Allow"

我看到您喜欢进行多次测试和实验,因此我将这种简单方法的解释留给您…… ;)

如果您想知道如何组合一个变量的扩展以将其用于另一个变量的进一步扩展,那么我建议您阅读 这个答案

Aacini
2018-10-27

哪里出错了

好吧,我找到了问题所在。我的 FOR 循环语句中有一个拼写错误。行:

FOR /F "tokens=*" %%A IN ("!LengthToSearch!") DO (SET "currentArg=!TestArgument:~%DelimIndex%,%%A!)

在我的文件中,导致问题的 SET 调用中的引号 ("") 不平衡。这导致程序崩溃,从而无法执行其余函数,并给我带来了很大的困惑。我通过尝试这个找到了它

FOR /F "tokens=*" %%A IN ("!LengthToSearch!") DO (echo %%A)

程序流程奇迹般地按预期工作。所以我重新调查了我的 for 循环并发现了缺失的引号。

嵌套延迟扩展变量怎么办?

建议的 for 循环方法(在某处,无法通过谷歌重新找到它)确实解决了嵌套延迟扩展问题。我不知道你可以将变量嵌套多深。但无论如何,执行:

FOR /F "tokens=*" %%A IN ("!LengthToSearch!") DO (SET "currentArg=!TestArgument:~%DelimIndex%,%%A!)

而不是:

SET "currentArg=!TestArgument:~%DelimIndex%,!LengthToSearch!!

实际上按预期工作!它正确拆分了两个索引值。我不记得在哪里提到过,但我记得解释是这样的:FOR 循环扩展发生在延迟变量扩展之前,但在 % 符号的初始扩展之后。因此,我可以将 for 循环参数放在延迟扩展操作中,它就会工作。我不知道它为什么会起作用,但这就是我的理解。我很感激任何额外的见解,如果我发现有关为什么会发生此问题以及我如何解决它的更多信息,我将编辑此答案。

仅供参考,我讨厌这样的无提示错误。我真的希望有更好的错误处理,因为批处理脚本功能强大但不必要地困难,因为几乎没有或根本没有好的错误检查。如果只有 notepad++ 显示变量中的不平衡引号。但供将来参考,至少这解决了尝试使用嵌套延迟扩展变量的任何问题。解决方案只是使用 for 循环变量作为中介。

如果有人找到关于延迟变量扩展行为和 for 循环行为的良好解释,我很乐意进行必要的编辑,这样人们就不会再遇到这个问题了

Redacted
2018-10-26