代码之家  ›  专栏  ›  技术社区  ›  Brandon

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

  •  2
  • Brandon  · 技术社区  · 6 年前

    我有一个显示所有*的批处理文件。pem文件位于一个目录中,用户可以选择其中一个,此时只需使用他们选择的数字向用户显示他们选择的文件,以反映数组的内容。我相信,由于它在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
    

    我知道“回声关闭了”而是输出,因为它将我的数组变量引用解释为空。 感谢您花时间阅读我的剧本,我可能把它复杂化了。

    2 回复  |  直到 6 年前
        1
  •  3
  •   aschipfl    6 年前

    你似乎很清楚 delayed expansion 因为你大部分时间都在正确使用它。

    然而,我通常将代码中的内容称为嵌套变量,因为批处理脚本中没有真正的数组,因为每个数组元素都只是一个单独的标量变量( pems[0] , pems[1] , pems[2] 等);所以我们也可以将类似的内容称为伪数组。

    不管怎么说 echo !pems[%selectedPem%]! 可以正确展开,除非它放在 selectedPem 在之前更新,因为这样我们就需要延迟扩展 选定PEM echo !pems[!selectedPem!]! 不起作用,因为它试图扩展变量 pems[ ] 选定PEM 被解释为文字字符串。

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

    现在让我们更进一步:扩展类似于 回响PEM[%selectedPem%]! 在代码行或代码块内部也会更新 选定PEM ,我们必须避免立即扩张。我们已经了解到延迟扩展不能嵌套,但还有一些其他扩展:

    1. 这个 call command 在延迟扩展之后引入另一个扩展阶段,该阶段再次处理 % -标志;所以完整的顺序是立即扩张,延迟扩张和另一种扩张 % -扩展。忽略第一个,让我们以一种方式利用后两个,即嵌套变量中的内部变量在外部变量之前展开,这意味着: call echo %%pems[!selectedPem!]%% .为了跳过即时扩展阶段,我们只需将 % 符号,每一个都被一个字面符号所取代,所以我们有 回显%pems[!selectedPem!]% 立即膨胀后。下一步是延迟扩张,然后是下一步 % -扩展。
      你应该注意到 呼叫 方法非常慢,因此大量使用可能会大幅降低脚本的整体性能。
    2. 扩张 for loop 变量发生在立即展开之后,但在延迟展开之前。让我们来总结一下 对于 循环,只迭代一次并返回 选定PEM ,就像这样: for %%Z in (!selectedPem!) do echo !pems[%%Z]! .这很有效,因为 对于 除非有通配符,否则不会访问文件系统 * ? 出现,但无论如何都不应在变量名或(伪)数组索引中使用。
      而不是标准 对于 for /F 也可用于: for /F %%Z in ("!selectedPem!") do echo !pems[%%Z]! (没有类似字符串的选项。) "delims=" 在情况下需要 选定PEM 预计只包含一个索引号)。
      A. for /L loop 也可以使用(当然,对于数字索引): for /L %%Z in (!selectedPem!,1,!selectedPem!) do echo !pems[%%Z]! .
    3. set /A command ,这意味着 % 也没有 ! 是读取变量所必需的,也可以使用,但仅当数组元素包含数值时(请注意 /A 代表算术)。因为这是特定于 设置/A ,这种扩展发生在命令执行期间,而命令执行又发生在延迟扩展之后。所以我们可以这样使用: set /A "pem=pems[!selectedPem!]" & echo !pem! .
    4. 为了完整起见,还有一种方法: 设置/A ,在中执行时 cmd 而不是在批处理上下文中,在控制台上输出(最后一个)结果;考虑到这构成了索引号,我们可以通过 for /F 然后像这样展开数组元素: for /F %%Z in ('set /A "selectedPem"') do echo !pems[%%Z]! . 设置/A 以新的方式执行 命令 实例依据 对于/F 所以这种方法当然不是最快的。

    关于这一主题,有一篇很好且全面的帖子,你应该仔细阅读: 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 没有必要(为了可读性,我建议不要使用与内部或外部命令相等的变量名),只需使用 光盘 立即在道路上;
    • 你错过了在队列中使用延迟扩展 set pems[%pemI%]=%%~nxp ,应该是 set pems[!pemI!]=%%~nxp pemI 在周围环境中更新 对于
    • 你要么需要延迟扩张 set /a pemI=%pemI%+1 也可以使用 设置/A 所以 set /A pemI=pemI+1 会起作用;然而,这甚至可以更加简化: set /A pemI+=1 ,这是完全等价的;
    • 我会使用不区分大小写的比较,尤其是当涉及到用户输入时,比如检查 Q ,也就是 if /I "!selectedPem!"=="q" ;
    • 现在,我们来到了一条需要我们以上所学知识的路线: ECHO !pems[%selectedPem%]! 需要成为 呼叫echo%%pems[!selectedPem!]%% ; 另一种使用 对于 也被插入到评论中( rem );
    • 还有另一条线路也有同样的问题: set privatekey=!pems[%selectedPem%]! 需要成为 call set privatekey=%%pems[!selectedPem!]%% (或基于 对于 也是);
    • 你又一次错过了延迟扩展,这次是排队 ECHO You chose %privatekey% ,应为 echo You chose !privatekey! ;
    • 最后,我改进了代码的引用,特别是 set 命令,最好这样写 set "VAR=Value" ,因此,任何不可见的尾随空格都不会成为值的一部分,如果值包含特殊字符,则该值本身也会受到保护,但引号不会成为值的一部分,这可能会特别影响连续性;

    下面是固定代码(包括所有原始代码 雷姆 备注(已删除):

    @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!
        )   
    )
    

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

        2
  •  0
  •   user6811411 user6811411    6 年前

    有了一些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
    
    推荐文章