在 IF 语句中对数组使用 DelayedExpansion 索引变量失败
using a DelayedExpansion index variable for an array within an IF statement fails
我有一个批处理文件,我在其中显示目录中的所有 *.pem 文件,让用户可以选择一个,此时只是试图通过使用数字向用户显示他们选择的文件他们选择 ECHO 数组的内容。我相信不知何故,因为它在 IF 语句内部,索引变量 "selectedPem" 没有在数组的索引括号中扩展,因为它使用的是 % 而不是使用延迟扩展!,这似乎在数组的索引括号。我认为这是因为如果我在 IF 语句之前设置 "selectedPem" 变量,它会正确回显。我认为唯一可行的选择是以某种方式使用子例程。为什么 selectedPem 变量在 IF 语句中不起作用?
@echo off
setlocal enabledelayedexpansion
set dir=%~dp0
cd %dir%
ECHO Performing initial search of private and public keys...
set pemI=0
set keyI=0
for %%p in (*.pem) do (
REM echo %%~nxp
set pems[%pemI%]=%%~nxp
REM echo !pems[%pemI%]!
set /a pemI=%pemI%+1
)
REM echo %pems[0]%
set /a totalPems=%pemI%-1
REM This is the test selectedPem variable that showed me the variable works
REM if I set it outside
REM of the "if defined pems[0]" statement
REM set selectedPem=
if defined pems[0] (
echo PEM Files Found:
for /l %%p in (0,1,%totalPems%) do (
echo %%p^) !pems[%%p]!
)
ECHO Select a pem file to be used as your private key. Otherwise, press
Q to not select one.
set /P selectedPem=Enter a number or Q:
IF "!selectedPem!"=="q" (
ECHO Skipping private key selection.
) ELSE (
ECHO !selectedPem!
ECHO %pems[0]%
REM The below ECHO only works when the above selectedPem is set
REM outside of the "IF defined" statement
ECHO !pems[%selectedPem%]!
REM Tried these other implementations:
REM ECHO !pems[!!selectedPem!!]!
REM ECHO %pems[!selectedPem!]%
REM setlocal disabledelayedexpansion
REM ECHO %pems[%%selectedPem%%]%
set privatekey=!pems[%selectedPem%]!
ECHO You chose %privatekey%
)
)`
输出:
Performing initial search of private and public keys...
PEM Files Found:
0) brandon.pem
Select a pem file to be used as your private key. Otherwise, press Q to not
select one.
Enter a number or Q:0
0
brandon.pem
ECHO is off.
You chose
我知道输出 "ECHO is off." 是因为它将我的数组变量引用解释为空。
感谢您花时间阅读我的脚本,我可能过于复杂了。
使用一些 goto 可以避免大多数(代码块)。
在无法做到这一点的情况下,使用 !
和另一种延迟扩展,并适当地加倍 %%
。
未测试:
@echo off
setlocal enabledelayedexpansion
set dir=%~dp0
cd %dir%
ECHO Performing initial search of private and public keys...
set pemI=0
set keyI=0
for %%p in (*.pem) do (
REM echo %%~nxp
set pems[!pemI!]=%%~nxp
REM call echo %%pems[!pemI!]%%
set /a pemI+=1
)
REM echo %pems[0]%
set /a totalPems=%pemI%
REM This is the test selectedPem variable that showed me the variable works
REM if I set it outside
REM of the "if defined pems[0]" statement
REM set selectedPem=
if pemI==0 (Echo no *.pem files found & goto :End)
echo PEM Files Found:
for /l %%p in (0,1,%totalPems%) do (
echo %%p^) !pems[%%p]!
)
ECHO Select a pem file to be used as your private key. Otherwise, press
Q to not select one.
set /P selectedPem=Enter a number or Q:
IF /I "!selectedPem!"=="q" (ECHO Skipping private key selection.& goto :End
ECHO !selectedPem!
ECHO %pems[0]%
REM The below ECHO only works when the above selectedPem is set
REM outside of the "IF defined" statement
ECHO !pems[%selectedPem%]!
REM Tried these other implementations:
REM ECHO !pems[!!selectedPem!!]!
REM ECHO %pems[!selectedPem!]%
REM setlocal disabledelayedexpansion
REM ECHO %pems[%%selectedPem%%]%
set privatekey=!pems[%selectedPem%]!
ECHO You chose %privatekey%
)
:end
REM ddo wahatever
pause
您似乎很了解 delayed expansion,因为您大部分时间都在正确使用它。
但是,我通常将代码中的内容称为嵌套变量,因为批处理脚本中没有真正的数组,因为每个数组元素不过是一个单独的标量变量(pems[0]
、pems[1]
, pems[2]
, 等等);所以我们也可以将类似的东西称为伪数组。
无论如何,像 echo !pems[%selectedPem%]!
这样的东西可以正确扩展,除非它被放置在 selectedPem
之前更新的行或代码块中,因为那样我们需要延迟扩展 selectedPem
还有。 echo !pems[!selectedPem!]!
不起作用,因为它试图扩展变量 pems[
和 ]
,而 selectedPem
被解释为文字字符串。
在继续之前,让我们先回到echo !pems[%selectedPem%]!
:为什么这是可扩展的?好吧,诀窍是我们这里有两个扩展阶段,正常或立即扩展 (%
),然后是延迟扩展 (!
)。重要的是顺序:嵌套变量的内部变量(因此是数组索引)必须在外部变量(因此是数组元素)之前被耗尽。像 echo %pems[!selectedPem!]%
这样的东西无法正确扩展,因为(很可能)没有名为 pems[!selectedPem!]
的变量,并且在将其扩展为空字符串后,两个 !
消失,因此延迟扩展阶段从未见过他们。
现在让我们更进一步:要在同时更新 selectedPem
的行或代码块中扩展类似于 echo !pems[%selectedPem%]!
的内容,我们必须避免立即扩展。我们已经知道延迟展开不能嵌套,但是还有一些其他的展开:
call
command在延迟扩展后引入了另一个扩展阶段,再次处理%
-signs;所以完整的序列是立即扩展、延迟扩展和另一个 %
-扩展。忽略第一个,让我们以嵌套变量的内部变量先于外部变量展开的方式使用后两个,意思是:call echo %%pems[!selectedPem!]%%
。为了跳过立即扩展阶段,我们只需将 %
符号加倍,每个符号都被一个文字替换,因此在立即扩展后我们有 echo %pems[!selectedPem!]%
。接下来就是延时扩容,那么说下%
-扩容。
您应该注意 call
方法非常慢,因此大量使用会大大降低脚本的整体性能。
for
loop 变量的扩展发生在立即扩展之后,延迟扩展之前。因此,让我们包装一个 for
循环,它只迭代一次 returns selectedPem
的值,就像这样:for %%Z in (!selectedPem!) do echo !pems[%%Z]!
。这是有效的,因为 for
不会访问文件系统,除非出现通配符 *
或 ?
,无论如何都不应该在变量名或(伪)数组索引中使用它们。
除了标准的 for
循环之外,还可以使用 for /F
:for /F %%Z in ("!selectedPem!") do echo !pems[%%Z]!
(不需要像 "delims="
这样的选项字符串,以防 selectedPem
需要包含只是一个索引号)。
也可以使用 for /L
loop(当然是用于数字索引):for /L %%Z in (!selectedPem!,1,!selectedPem!) do echo !pems[%%Z]!
.
- 由
set /A
command建立的隐式扩展,意味着%
和!
都不是读取变量所必需的,也可以使用,但前提是数组元素包含数字值(注意 /A
代表算术)。由于这是 set /A
特有的功能,因此这种扩展发生在命令执行期间,而这又发生在延迟扩展之后。所以我们可以这样使用:set /A "pem=pems[!selectedPem!]" & echo !pem!
.
- 为了完整起见,这里还有一种方法:
set /A
,当在cmd
而不是批处理上下文中执行时,在控制台上输出(最后)结果;鉴于这构成了索引号,我们可以通过 for /F
捕获它并像这样扩展数组元素:for /F %%Z in ('set /A "selectedPem"') do echo !pems[%%Z]!
。 set /A
由 for /F
在新的 cmd
实例中执行,因此这种方法当然不是最快的方法。
关于这个主题,有一篇非常全面的 post,您应该仔细阅读它的详细信息:Arrays, linked lists and other data structures in cmd.exe (batch) script。
对于命令行和批处理脚本的解析以及一般的变量扩展,请参考这个很棒的线程:How does the Windows Command Interpreter (CMD.EXE) parse scripts?
现在让我们看看您的代码。有几个问题:
- 使用
cd /D
而不是 cd
以便在必要时更改驱动器;顺便说一句,临时变量 dir
不是必需的(为了便于阅读,我建议不要使用等同于内部或外部命令的变量名),只需立即在路径上使用 cd
;
- 您错过了在行
set pems[%pemI%]=%%~nxp
中使用延迟扩展,它应该显示为 set pems[!pemI!]=%%~nxp
,因为 pemI
在周围的 for
循环中更新;
- 你也需要在行
set /a pemI=%pemI%+1
中延迟扩展,或者你使用 set /A
的隐式变量扩展,所以 set /A pemI=pemI+1
可以工作;然而,这甚至可以更简化:set /A pemI+=1
,这是完全等价的;
- 我会使用不区分大小写的比较,尤其是在涉及用户输入时,例如您检查
Q
,这将是 if /I "!selectedPem!"=="q"
;
- 现在我们来到一行,需要我们在上面学到的东西:
ECHO !pems[%selectedPem%]!
需要变成 call echo %%pems[!selectedPem!]%%
;在评论中也插入了使用 for
的替代方法 (rem
);
- 然后还有一行同样的问题:
set privatekey=!pems[%selectedPem%]!
需要变成call set privatekey=%%pems[!selectedPem!]%%
(或者也是基于for
的方法);
- 您又一次错过了使用延迟扩展的机会,这次是在
ECHO You chose %privatekey%
行,应该是 echo You chose !privatekey!
;
- 最后我改进了你代码的引用,特别是
set
命令的引用,最好像 set "VAR=Value"
这样写,这样任何不可见的尾随空格都不会成为值的一部分, 如果值本身包含特殊字符,则值本身会受到保护,但引号不会成为值的一部分,这可能会干扰特别是串联;
所以这是固定代码(删除了所有原始 rem
评论):
@echo off
setlocal EnableDelayedExpansion
cd /D "%~dp0"
echo Performing initial search of private and public keys...
set "pemI=0"
set "keyI=0"
for %%p in (*.pem) do (
set "pems[!pemI!]=%%~nxp"
set /A "pemI+=1"
)
set /A "totalPems=pemI-1"
if defined pems[0] (
echo PEM Files Found:
for /L %%p in (0,1,%totalPems%) do (
echo %%p^) !pems[%%p]!
)
set /P selectedPem="Enter a number or Q: "
if /I "!selectedPem!"=="q" (
echo Skipping private key selection.
) else (
echo !selectedPem!
echo %pems[0]%
call echo %%pems[!selectedPem!]%%
rem for %%Z in (!selectedPem!) do echo !pems[%%Z]!
call set "privatekey=%%pems[!selectedPem!]%%"
rem for %%Z in (!selectedPem!) do set "privatekey=!pems[%%Z]!"
echo You chose !privatekey!
)
)
我不得不承认我没有详细检查你脚本的逻辑。
我有一个批处理文件,我在其中显示目录中的所有 *.pem 文件,让用户可以选择一个,此时只是试图通过使用数字向用户显示他们选择的文件他们选择 ECHO 数组的内容。我相信不知何故,因为它在 IF 语句内部,索引变量 "selectedPem" 没有在数组的索引括号中扩展,因为它使用的是 % 而不是使用延迟扩展!,这似乎在数组的索引括号。我认为这是因为如果我在 IF 语句之前设置 "selectedPem" 变量,它会正确回显。我认为唯一可行的选择是以某种方式使用子例程。为什么 selectedPem 变量在 IF 语句中不起作用?
@echo off
setlocal enabledelayedexpansion
set dir=%~dp0
cd %dir%
ECHO Performing initial search of private and public keys...
set pemI=0
set keyI=0
for %%p in (*.pem) do (
REM echo %%~nxp
set pems[%pemI%]=%%~nxp
REM echo !pems[%pemI%]!
set /a pemI=%pemI%+1
)
REM echo %pems[0]%
set /a totalPems=%pemI%-1
REM This is the test selectedPem variable that showed me the variable works
REM if I set it outside
REM of the "if defined pems[0]" statement
REM set selectedPem=
if defined pems[0] (
echo PEM Files Found:
for /l %%p in (0,1,%totalPems%) do (
echo %%p^) !pems[%%p]!
)
ECHO Select a pem file to be used as your private key. Otherwise, press
Q to not select one.
set /P selectedPem=Enter a number or Q:
IF "!selectedPem!"=="q" (
ECHO Skipping private key selection.
) ELSE (
ECHO !selectedPem!
ECHO %pems[0]%
REM The below ECHO only works when the above selectedPem is set
REM outside of the "IF defined" statement
ECHO !pems[%selectedPem%]!
REM Tried these other implementations:
REM ECHO !pems[!!selectedPem!!]!
REM ECHO %pems[!selectedPem!]%
REM setlocal disabledelayedexpansion
REM ECHO %pems[%%selectedPem%%]%
set privatekey=!pems[%selectedPem%]!
ECHO You chose %privatekey%
)
)`
输出:
Performing initial search of private and public keys...
PEM Files Found:
0) brandon.pem
Select a pem file to be used as your private key. Otherwise, press Q to not
select one.
Enter a number or Q:0
0
brandon.pem
ECHO is off.
You chose
我知道输出 "ECHO is off." 是因为它将我的数组变量引用解释为空。 感谢您花时间阅读我的脚本,我可能过于复杂了。
使用一些 goto 可以避免大多数(代码块)。
在无法做到这一点的情况下,使用 !
和另一种延迟扩展,并适当地加倍 %%
。
未测试:
@echo off
setlocal enabledelayedexpansion
set dir=%~dp0
cd %dir%
ECHO Performing initial search of private and public keys...
set pemI=0
set keyI=0
for %%p in (*.pem) do (
REM echo %%~nxp
set pems[!pemI!]=%%~nxp
REM call echo %%pems[!pemI!]%%
set /a pemI+=1
)
REM echo %pems[0]%
set /a totalPems=%pemI%
REM This is the test selectedPem variable that showed me the variable works
REM if I set it outside
REM of the "if defined pems[0]" statement
REM set selectedPem=
if pemI==0 (Echo no *.pem files found & goto :End)
echo PEM Files Found:
for /l %%p in (0,1,%totalPems%) do (
echo %%p^) !pems[%%p]!
)
ECHO Select a pem file to be used as your private key. Otherwise, press
Q to not select one.
set /P selectedPem=Enter a number or Q:
IF /I "!selectedPem!"=="q" (ECHO Skipping private key selection.& goto :End
ECHO !selectedPem!
ECHO %pems[0]%
REM The below ECHO only works when the above selectedPem is set
REM outside of the "IF defined" statement
ECHO !pems[%selectedPem%]!
REM Tried these other implementations:
REM ECHO !pems[!!selectedPem!!]!
REM ECHO %pems[!selectedPem!]%
REM setlocal disabledelayedexpansion
REM ECHO %pems[%%selectedPem%%]%
set privatekey=!pems[%selectedPem%]!
ECHO You chose %privatekey%
)
:end
REM ddo wahatever
pause
您似乎很了解 delayed expansion,因为您大部分时间都在正确使用它。
但是,我通常将代码中的内容称为嵌套变量,因为批处理脚本中没有真正的数组,因为每个数组元素不过是一个单独的标量变量(pems[0]
、pems[1]
, pems[2]
, 等等);所以我们也可以将类似的东西称为伪数组。
无论如何,像 echo !pems[%selectedPem%]!
这样的东西可以正确扩展,除非它被放置在 selectedPem
之前更新的行或代码块中,因为那样我们需要延迟扩展 selectedPem
还有。 echo !pems[!selectedPem!]!
不起作用,因为它试图扩展变量 pems[
和 ]
,而 selectedPem
被解释为文字字符串。
在继续之前,让我们先回到echo !pems[%selectedPem%]!
:为什么这是可扩展的?好吧,诀窍是我们这里有两个扩展阶段,正常或立即扩展 (%
),然后是延迟扩展 (!
)。重要的是顺序:嵌套变量的内部变量(因此是数组索引)必须在外部变量(因此是数组元素)之前被耗尽。像 echo %pems[!selectedPem!]%
这样的东西无法正确扩展,因为(很可能)没有名为 pems[!selectedPem!]
的变量,并且在将其扩展为空字符串后,两个 !
消失,因此延迟扩展阶段从未见过他们。
现在让我们更进一步:要在同时更新 selectedPem
的行或代码块中扩展类似于 echo !pems[%selectedPem%]!
的内容,我们必须避免立即扩展。我们已经知道延迟展开不能嵌套,但是还有一些其他的展开:
call
command在延迟扩展后引入了另一个扩展阶段,再次处理%
-signs;所以完整的序列是立即扩展、延迟扩展和另一个%
-扩展。忽略第一个,让我们以嵌套变量的内部变量先于外部变量展开的方式使用后两个,意思是:call echo %%pems[!selectedPem!]%%
。为了跳过立即扩展阶段,我们只需将%
符号加倍,每个符号都被一个文字替换,因此在立即扩展后我们有echo %pems[!selectedPem!]%
。接下来就是延时扩容,那么说下%
-扩容。
您应该注意call
方法非常慢,因此大量使用会大大降低脚本的整体性能。for
loop 变量的扩展发生在立即扩展之后,延迟扩展之前。因此,让我们包装一个for
循环,它只迭代一次 returnsselectedPem
的值,就像这样:for %%Z in (!selectedPem!) do echo !pems[%%Z]!
。这是有效的,因为for
不会访问文件系统,除非出现通配符*
或?
,无论如何都不应该在变量名或(伪)数组索引中使用它们。
除了标准的for
循环之外,还可以使用for /F
:for /F %%Z in ("!selectedPem!") do echo !pems[%%Z]!
(不需要像"delims="
这样的选项字符串,以防selectedPem
需要包含只是一个索引号)。
也可以使用for /L
loop(当然是用于数字索引):for /L %%Z in (!selectedPem!,1,!selectedPem!) do echo !pems[%%Z]!
.- 由
set /A
command建立的隐式扩展,意味着%
和!
都不是读取变量所必需的,也可以使用,但前提是数组元素包含数字值(注意/A
代表算术)。由于这是set /A
特有的功能,因此这种扩展发生在命令执行期间,而这又发生在延迟扩展之后。所以我们可以这样使用:set /A "pem=pems[!selectedPem!]" & echo !pem!
. - 为了完整起见,这里还有一种方法:
set /A
,当在cmd
而不是批处理上下文中执行时,在控制台上输出(最后)结果;鉴于这构成了索引号,我们可以通过for /F
捕获它并像这样扩展数组元素:for /F %%Z in ('set /A "selectedPem"') do echo !pems[%%Z]!
。set /A
由for /F
在新的cmd
实例中执行,因此这种方法当然不是最快的方法。
关于这个主题,有一篇非常全面的 post,您应该仔细阅读它的详细信息:Arrays, linked lists and other data structures in cmd.exe (batch) script。
对于命令行和批处理脚本的解析以及一般的变量扩展,请参考这个很棒的线程:How does the Windows Command Interpreter (CMD.EXE) parse scripts?
现在让我们看看您的代码。有几个问题:
- 使用
cd /D
而不是cd
以便在必要时更改驱动器;顺便说一句,临时变量dir
不是必需的(为了便于阅读,我建议不要使用等同于内部或外部命令的变量名),只需立即在路径上使用cd
; - 您错过了在行
set pems[%pemI%]=%%~nxp
中使用延迟扩展,它应该显示为set pems[!pemI!]=%%~nxp
,因为pemI
在周围的for
循环中更新; - 你也需要在行
set /a pemI=%pemI%+1
中延迟扩展,或者你使用set /A
的隐式变量扩展,所以set /A pemI=pemI+1
可以工作;然而,这甚至可以更简化:set /A pemI+=1
,这是完全等价的; - 我会使用不区分大小写的比较,尤其是在涉及用户输入时,例如您检查
Q
,这将是if /I "!selectedPem!"=="q"
; - 现在我们来到一行,需要我们在上面学到的东西:
ECHO !pems[%selectedPem%]!
需要变成call echo %%pems[!selectedPem!]%%
;在评论中也插入了使用for
的替代方法 (rem
); - 然后还有一行同样的问题:
set privatekey=!pems[%selectedPem%]!
需要变成call set privatekey=%%pems[!selectedPem!]%%
(或者也是基于for
的方法); - 您又一次错过了使用延迟扩展的机会,这次是在
ECHO You chose %privatekey%
行,应该是echo You chose !privatekey!
; - 最后我改进了你代码的引用,特别是
set
命令的引用,最好像set "VAR=Value"
这样写,这样任何不可见的尾随空格都不会成为值的一部分, 如果值本身包含特殊字符,则值本身会受到保护,但引号不会成为值的一部分,这可能会干扰特别是串联;
所以这是固定代码(删除了所有原始 rem
评论):
@echo off
setlocal EnableDelayedExpansion
cd /D "%~dp0"
echo Performing initial search of private and public keys...
set "pemI=0"
set "keyI=0"
for %%p in (*.pem) do (
set "pems[!pemI!]=%%~nxp"
set /A "pemI+=1"
)
set /A "totalPems=pemI-1"
if defined pems[0] (
echo PEM Files Found:
for /L %%p in (0,1,%totalPems%) do (
echo %%p^) !pems[%%p]!
)
set /P selectedPem="Enter a number or Q: "
if /I "!selectedPem!"=="q" (
echo Skipping private key selection.
) else (
echo !selectedPem!
echo %pems[0]%
call echo %%pems[!selectedPem!]%%
rem for %%Z in (!selectedPem!) do echo !pems[%%Z]!
call set "privatekey=%%pems[!selectedPem!]%%"
rem for %%Z in (!selectedPem!) do set "privatekey=!pems[%%Z]!"
echo You chose !privatekey!
)
)
我不得不承认我没有详细检查你脚本的逻辑。