在IF语句中为数组使用DelayedExpansion索引变量失败

时间:2018-05-17 16:55:37

标签: batch-file if-statement delayedvariableexpansion

我有一个批处理文件,我在目录中显示所有* .pem文件,为用户提供选择一个文件的选项,此时只是尝试使用他们选择的数字向用户显示他们选择的文件到ECHO数组的内容。我相信不知何故因为它在IF语句中,索引变量" selectedPem"没有在数组的索引括号中扩展,因为它使用%并且没有使用延迟扩展!,这似乎不适用于数组的索引括号。我想这是因为如果我设置" selectedPem" IF语句之前的变量,它正确的ECHO。我认为这个工作的唯一选择是以某种方式利用子程序。为什么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已关闭。"而是输出,因为它将我的数组变量引用解释为空。 感谢您抽出时间阅读我的剧本,我可能过于复杂了。

2 个答案:

答案 0 :(得分:1)

您似乎非常了解delayed expansion,因为您大部分时间都在使用它。

但是,你的代码中我通常会调用嵌套变量,因为批处理脚本中没有真正的数组,因为每个数组元素只是一个单独的标量变量(pems[0]pems[1]pems[2]等);所以我们也可以将这样的东西称为伪数组。

无论如何,像echo !pems[%selectedPem%]!这样的东西可以正确扩展,除非它被放置在selectedPem之前更新的代码行或代码块中,因为那时我们需要{{1}的延迟扩展也是。 selectedPem不起作用,因为它会尝试扩展变量echo !pems[!selectedPem!]!pems[,而]会被解释为文字字符串。

在继续前进之前,让我们先回到selectedPem:为什么这可以扩展?好吧,诀窍是我们在这里有两个扩展阶段,正常或立即扩展(echo !pems[%selectedPem%]!),然后是延迟扩展(%)。重要的是序列:嵌套变量的内部一个(因此数组索引)必须在外部变量之前消耗(因此数组元素)。像!这样的东西无法正确扩展,因为(很可能)没有名为echo %pems[!selectedPem!]%的变量,并且在将其扩展为空字符串后,两个pems[!selectedPem!]消失,因此延迟扩展阶段永远不会看到它们。

现在让我们更进一步:在同样更新!的代码行或代码块中展开类似于echo !pems[%selectedPem%]!的内容,我们必须避免立即扩展。我们已经知道延迟扩展不能嵌套,但还有其他一些扩展:

  1. call command在延迟扩张后引入另一个扩张阶段,再次处理selectedPem - 标志;所以完整的序列是立即扩展,延迟扩展和另一个% - 扩展。忽略第一个,让我们以嵌套变量的内部一个在外部变量之前展开的方式利用后两个,意思是:%。要跳过立即扩展阶段,我们只需将call echo %%pems[!selectedPem!]%%符号加倍,每个符号由一个文字替换,因此我们在立即扩展后会%。下一步是延迟扩张,然后是下一个echo %pems[!selectedPem!]% - 扩张 您应该注意%方法非常慢,因此大量使用会大大降低脚本的整体性能。
  2. for loop变量的扩展发生在立即扩展之后,但是在延迟扩展之前。因此,让我们只围绕迭代一次call循环并返回for的值,如下所示:selectedPem。这是有效的,因为for %%Z in (!selectedPem!) do echo !pems[%%Z]!不会访问文件系统,除非出现通配符for*,不应该在变量名或(伪)数组索引中使用。
    可以使用?代替标准for循环:for /F %%Z in ("!selectedPem!") do echo !pems[%%Z]!(如果"delims="预计包含selectedPem,则不需要for /L %%Z in (!selectedPem!,1,!selectedPem!) do echo !pems[%%Z]!之类的选项字符串只是一个索引号) 也可以使用for /F(对于数字索引,当然):%
  3. for /L loop建立的隐式扩展,意味着读取变量不需要!/A,也可以使用,但前提是数组元素包含数值(请注意set /A代表算术)。由于这是set /A "pem=pems[!selectedPem!]" & echo !pem!特有的功能,因此在命令执行期间会发生这种扩展,而这种扩展又会在延迟扩展后发生。所以我们可以这样使用:set /A
  4. 仅仅为了完整起见,还有一种方法:cmd,在for /F %%Z in ('set /A "selectedPem"') do echo !pems[%%Z]!而不是批处理上下文中执行时,在控制台上输出(最后)结果;鉴于这构成了索引号,我们可以通过set /A command捕获它并扩展数组元素,如下所示:set /Acmd在新的for /F实例中执行cd /D,因此这种方法当然不是最快的。
  5. 有一篇关于这个主题的精彩而全面的文章,你应该仔细阅读它:for /F

    对于解析命令行和批处理脚本以及一般的变量扩展,请参阅这个很棒的主题:Arrays, linked lists and other data structures in cmd.exe (batch) script

    现在让我们来看看你的代码。有几个问题:

    • 使用cd代替dir,以便在必要时更改驱动器;顺便说一下,临时变量cd不是必需的(让我建议不要使用等于内部或外部命令的变量名以便于阅读),只需在路径上立即使用set pems[%pemI%]=%%~nxp; < / LI>
    • 您错过了第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! ) ) 备注):

    {{1}}

    我不得不承认我没有详细检查你的脚本的逻辑。

答案 1 :(得分:0)

对于一些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