在尝试修复嵌套延迟扩展问题时,在函数的 else 情况下添加 for 循环导致的莫名其妙的行为
Inexplicable behavior caused by addition of for-loop inside else case of a function while trying to fix nested delayed expansion problem
前言
我是批处理的新手,已经做了大约 2 周了。当我向其添加特定的 for 循环时,我的函数没有遵循我认为正常的程序流规则。删除 for 循环后,该函数遵循我期望的程序流程,但输出仍然不是我想要的。根本问题是延迟扩展变量内部的延迟扩展变量。
我已经多次检查括号,但在靠近底部的完整代码中我找不到任何不平衡的地方。从程序流程的角度来看,这是没有意义的,添加 for 循环会影响 for 循环之前的语句。我唯一能想到的是发生了一些奇怪的错误,代码根本没有执行完整的循环并在循环之后立即到达我的暂停语句。
我实际上想做什么
我试图用 ; 分隔一行字符串在我想稍后分隔的字符串中有 unknown/variable 个标记和其他可能的分隔符(如逗号)。这不能用 batch 预制的 for 循环来完成,FOR /F 只能做行和/或设置标记的数量,而常规的 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 值,然后 expand/run/do 子字符串操作的扩展并将其设置为 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!"==";"
仅 "runs" 一次(实际上并没有 运行 它应有的全部)然后循环就中断了。我并不是说它会到达循环的末尾,它只是完全忽略了整个函数的其余部分。我在此处的完整代码中多次检查了括号:
函数的完整代码
: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
和 none 在第二个 IF (IF NOT "!TestArgument:~%CurrentIndex%,1!"==";"
) 的 ELSE 情况下的回声,除了第二个 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%
我非常沮丧,我只需要一些帮助。我会继续尝试各种事情,但这是我需要完成的程序的最后一部分,我只想获得稳定、工作和高效的东西,而不必做我以前做过的事情,使用行列和幻数来检查通过令牌设置防火墙。 (我正在测试一个脚本来配置一些设置,以便可以设置公司网络)。
怎么了
好的,我发现了问题。我 have/had 在我的 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循环方法(在某处,无法通过google重新找到它)确实解决了嵌套延迟扩展问题。我不知道你可以嵌套多深的变量。但无论如何,做:
FOR /F "tokens=*" %%A IN ("!LengthToSearch!") DO (SET "currentArg=!TestArgument:~%DelimIndex%,%%A!)
而不是:
SET "currentArg=!TestArgument:~%DelimIndex%,!LengthToSearch!!
确实按预期工作!它使用正确代入的两个索引值拆分字符串。我不记得在哪里提到过,但我记得解释是这样的:FOR 循环扩展发生在延迟变量扩展之前,但发生在 % 符号的初始扩展之后。因此,我可以将 for 循环参数放在延迟扩展操作中,它会起作用。我不明白为什么它会起作用,但这就是我对它的理解。我将不胜感激任何额外的见解,如果我找到有关此问题发生的原因以及我如何解决它的更多信息,我将编辑此答案。
郑重声明,我讨厌这样的静默错误。我真的希望有更好的错误处理,因为批处理脚本功能强大但不必要地困难,因为几乎没有好的错误检查。如果只有 notepad++ 在变量中显示不平衡的引号。但为了将来参考,至少这解决了尝试使用嵌套延迟扩展变量的任何问题。解决办法就是简单的使用for循环变量作为中介。
如果有人找到对延迟变量扩展行为和 for 循环行为的解释的很好的参考,我很乐意进行必要的编辑,这样人们就不会再遇到这个问题了
如果你想在分号处分割一个字符串,你可以使用这个简单的方法:
@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"
我看到你喜欢做多次测试和实验,所以我把这个简单方法的解释留给你...... ;)
如果你想知道如何将一个变量的扩展结合起来用于另一个变量的进一步扩展,那么我建议你阅读this answer。
前言
我是批处理的新手,已经做了大约 2 周了。当我向其添加特定的 for 循环时,我的函数没有遵循我认为正常的程序流规则。删除 for 循环后,该函数遵循我期望的程序流程,但输出仍然不是我想要的。根本问题是延迟扩展变量内部的延迟扩展变量。
我已经多次检查括号,但在靠近底部的完整代码中我找不到任何不平衡的地方。从程序流程的角度来看,这是没有意义的,添加 for 循环会影响 for 循环之前的语句。我唯一能想到的是发生了一些奇怪的错误,代码根本没有执行完整的循环并在循环之后立即到达我的暂停语句。
我实际上想做什么
我试图用 ; 分隔一行字符串在我想稍后分隔的字符串中有 unknown/variable 个标记和其他可能的分隔符(如逗号)。这不能用 batch 预制的 for 循环来完成,FOR /F 只能做行和/或设置标记的数量,而常规的 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 值,然后 expand/run/do 子字符串操作的扩展并将其设置为 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!"==";"
仅 "runs" 一次(实际上并没有 运行 它应有的全部)然后循环就中断了。我并不是说它会到达循环的末尾,它只是完全忽略了整个函数的其余部分。我在此处的完整代码中多次检查了括号:
函数的完整代码
: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
和 none 在第二个 IF (IF NOT "!TestArgument:~%CurrentIndex%,1!"==";"
) 的 ELSE 情况下的回声,除了第二个 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%
我非常沮丧,我只需要一些帮助。我会继续尝试各种事情,但这是我需要完成的程序的最后一部分,我只想获得稳定、工作和高效的东西,而不必做我以前做过的事情,使用行列和幻数来检查通过令牌设置防火墙。 (我正在测试一个脚本来配置一些设置,以便可以设置公司网络)。
怎么了
好的,我发现了问题。我 have/had 在我的 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循环方法(在某处,无法通过google重新找到它)确实解决了嵌套延迟扩展问题。我不知道你可以嵌套多深的变量。但无论如何,做:
FOR /F "tokens=*" %%A IN ("!LengthToSearch!") DO (SET "currentArg=!TestArgument:~%DelimIndex%,%%A!)
而不是:
SET "currentArg=!TestArgument:~%DelimIndex%,!LengthToSearch!!
确实按预期工作!它使用正确代入的两个索引值拆分字符串。我不记得在哪里提到过,但我记得解释是这样的:FOR 循环扩展发生在延迟变量扩展之前,但发生在 % 符号的初始扩展之后。因此,我可以将 for 循环参数放在延迟扩展操作中,它会起作用。我不明白为什么它会起作用,但这就是我对它的理解。我将不胜感激任何额外的见解,如果我找到有关此问题发生的原因以及我如何解决它的更多信息,我将编辑此答案。
郑重声明,我讨厌这样的静默错误。我真的希望有更好的错误处理,因为批处理脚本功能强大但不必要地困难,因为几乎没有好的错误检查。如果只有 notepad++ 在变量中显示不平衡的引号。但为了将来参考,至少这解决了尝试使用嵌套延迟扩展变量的任何问题。解决办法就是简单的使用for循环变量作为中介。
如果有人找到对延迟变量扩展行为和 for 循环行为的解释的很好的参考,我很乐意进行必要的编辑,这样人们就不会再遇到这个问题了
如果你想在分号处分割一个字符串,你可以使用这个简单的方法:
@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"
我看到你喜欢做多次测试和实验,所以我把这个简单方法的解释留给你...... ;)
如果你想知道如何将一个变量的扩展结合起来用于另一个变量的进一步扩展,那么我建议你阅读this answer。