对IF语句中的数组使用DelayedExpansion索引变量失败 [英] using a DelayedExpansion index variable for an array within an IF statement fails

查看:170
本文介绍了对IF语句中的数组使用DelayedExpansion索引变量失败的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个批处理文件,我在目录中显示所有* .pem文件,让用户可以选择一个,并且此时只是尝试使用他们选择的数字向用户显示他们选择的文件到ECHO数组的内容。我认为,不知何故因为它在IF语句中,索引变量selectedPem没有在数组的索引括号中扩展,因为它使用的是%并且没有使用延迟扩展!,这似乎不适用于数组的索引括号。我认为这是因为如果我在IF语句之前设置selectedPem变量,那么它就是ECHO。我认为这个工作的唯一选择是以某种方式使用子程序。为什么selectedPem变量在IF语句中不起作用?

I have a batch file where I am displaying all *.pem files in a directory, giving the user an option to choose one, and at this time just trying to show the user what file they picked by using the number they chose to ECHO the contents of the array. I believe that somehow because it is inside of the IF statement, the index variable "selectedPem" is not expanding in the array's index brackets because it is using the % and not using the delayed expansion !, which doesnt appear to work in the array's index brackets. I think this because if I set the "selectedPem" variable before the IF statement, it ECHOs properly. I think the only option for this to work is to somehow make use of a subroutine. Why is selectedPem variable not working inside of the IF statement?

@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已关闭。而是输出,因为它将我的数组变量引用解释为空。
我很感激你花时间阅读我的剧本,我可能过于复杂了。

I understand that "ECHO is off." is outputted instead because it is interpreting my array variable reference as empty. I appreciate your time in reading over my script, I am likely over-complicating it.

推荐答案

您似乎非常了解延迟扩展大多数时候你正确地使用它。

You appear to be well aware of delayed expansion since you are correctly using it most of the time.

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

However, what you have in your code I usually call nested variables, because there are no real arrays in batch scripting since every array element is nothing but an individual scalar variable (pems[0], pems[1], pems[2], etc.); so we might refer to something like this also as pseudo-arrays.

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

Anyway, something like echo !pems[%selectedPem%]! can correctly expanded, unless it is placed within a line or block of code in which selectedPem becomes updated before, because then we would require delayed expansion for selectedPem as well. echo !pems[!selectedPem!]! does not work, because it tries to expand variables pems[ and ], and selectedPem is interpreted as a literal string.

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

Before moving forward, let us first go back to echo !pems[%selectedPem%]!: why is this expandable? Well, the trick is we have two expansion phases here, the normal or immediate expansion (%), followed by the delayed expansion (!). The important thing is the sequence: the inner one of the nested variables (hence the array index) must be expended before the outer one (so the array element). Something like echo %pems[!selectedPem!]% cannot be expanded correctly, because there is (most likely) no variable named pems[!selectedPem!], and after expanding it to an empty string, the two ! disappear and so the delayed expansion phase does never see them.

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

Now let us go a step further: to expand something similar to echo !pems[%selectedPem%]! inside of a line or block of code that also updates selectedPem, we must avoid immediate expansion. We already learned that delayed expansion cannot be nested, but there are some other expansions:


  1. 调用命令在延迟扩展后引入另一个扩展阶段,处理 -signs;所以完整的序列是立即扩展,延迟扩展和另一个 -expansion。忽略第一个,让我们使用后两个,使嵌套变量的内部变量在外部变量之前展开,意思是: call echo %% pems [!selectedPem!]% %。要跳过立即扩展阶段,我们只需将符号加倍,每个符号都用一个字面值替换,所以我们有 echo%pems [!selectedPem !]%立即扩展后。下一步是延迟扩展,然后是下一个 -expansion。

    您应该注意调用方法非常慢,因此大量使用会大大降低脚本的整体性能。

  2. 扩展 for loop 变量在立即扩展后发生,但在延迟扩展之前。因此,让我们为循环一个循环,只返回一次迭代并返回 selectedPem 的值,如下所示: for %% Z in(!selectedPem!)do echo!pems [%% Z]!。这是有效的,因为 for 不会访问文件系统,除非通配符 * 出现?,无论如何都不应该在变量名或(伪)数组索引中使用。

    而不是 c>循环, for / F 也可以使用: for / F %% Z in(!selectedPem!)do echo!pems [%% Z]!(如果 selectedPem 预计仅包含索引号,则不需要像delims =这样的选项字符串。

    / L 循环也可以使用(对于数字索引,当然): for / L %% Z in(!selectedPem!,1,!selectedPem!)do echo!pems [%% Z]!

  3. set / A 命令,意味着都不需要读取变量也可以使用,但仅当数组元素包含数值时(注意 / A 代表算术)。由于这是 set / A 特有的功能,因此在命令执行期间会发生这种扩展,而这种扩展又会在延迟扩展后发生。所以我们可以这样使用: set / Apem = pems [!selectedPem!]& echo!pem!

  4. 为了完整起见,这里还有一个方法: set / A ,在 cmd 中执行时,而不是在批处理上下文中,在控制台上输出(最后)结果;鉴于这构成了索引号,我们可以通过 for / F 并像这样展开数组元素: for / F %% Z in('set / AselectedPem')做echo! PEMS [%% Z]! set / A 在新的 cmd 实例中执行 for / F ,所以这种方法当然不是最快的。

  1. The call command introduces another expansion phase after delayed expansion, which again handles %-signs; so the full sequence is immediate expansion, delayed expansion and another %-expansion. Dismissing the first one, let us make use of the latter two in a way that the inner one of the nested variables becomes expanded before the outer one, meaning: call echo %%pems[!selectedPem!]%%. To skip the immediate expansion phase we simply double the % signs, which become replaced by a literal one each, so we have echo %pems[!selectedPem!]% after immediate expansion. The next step is delayed expansion, then the said next %-expansion.
    You should take notice that the call method is quite slow, so heavy usage could drastically reduce the overall performance of your script.
  2. Expansion of for loop variables happens after immediate expansion, but before delayed expansion. So let us wrap a for loop around that iterates once only and returns the value of selectedPem, like this: for %%Z in (!selectedPem!) do echo !pems[%%Z]!. This works, because for does not access the file system unless a wild-card * or ? appears, which should not be used in variable names or (pseudo-)array indexes anyway.
    Instead of a standard for loop, for /F could be used also: for /F %%Z in ("!selectedPem!") do echo !pems[%%Z]! (there is no option string like "delims=" required in case selectedPem is expected to contain just an index number).
    A for /L loop could be used too (for numeric indexes, of course): for /L %%Z in (!selectedPem!,1,!selectedPem!) do echo !pems[%%Z]!.
  3. Implicit expansion established by the set /A command, meaning that neither % nor ! is necessary to read variables, can be used too, but only if the array element contains a numeric value (note that /A stands for arithmetics). Since this is a feature specific to set /A, this kind of expansion happens during command execution, which in turn happens after delayed expansion. So we can use that like this: set /A "pem=pems[!selectedPem!]" & echo !pem!.
  4. Just for the sake of completeness, here is one more way: set /A, when executed in cmd rather than in batch context, outputs the (last) result on the console; given that this constitutes the index number, we could capture this by for /F and expand the array element like this: for /F %%Z in ('set /A "selectedPem"') do echo !pems[%%Z]!. set /A is executed in a new cmd instance by for /F, so this approach is not the fastest one, of course.

关于这个主题有一篇很棒的综合帖子你应该详细介绍一下:数组,链接列表和cmd.exe(批处理)脚本中的其他数据结构

There is a great and comprehensive post concerning this topic which you should go through it detail: Arrays, linked lists and other data structures in cmd.exe (batch) script.

对于解析命令行和批处理脚本以及一般的变量扩展,请参阅此真棒线程: Windows命令解释器(CMD.EXE)如何?解析脚本?

For parsing of command lines and batch scripts as well as variable expansion in general, refer to this awesome thread: How does the Windows Command Interpreter (CMD.EXE) parse scripts?

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

Now let us take a look at your code. There are several issues:


  • 使用 cd / D 而不是 cd 以便在必要时更改驱动器;顺便说一句,临时变量 dir 是没有必要的(让我建议不要使用与内部或外部命令相同的变量名以便于阅读),只需使用 cd 立即在路径上;

  • 你错过了使用行的延迟扩展设置pems [%pemI%] =% %~nxp ,它应该读取 set pems [!pemI!] = %% ~nxp as pemI 周围更新循环;

  • 你需要在行集中延迟扩展/ a pemI =%pemI%+ 1 ,或者你使用隐式变量扩展 set / 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你选择%privatekey%,这应该是 echo你选择了!privatekey!;

  • 最后我改进了你的代码的引用,特别是 set 命令的引用,这应该更好地写成设置VAR = Value,因此任何不可见的尾随空格都不会成为值的一部分,如果值包含特殊字符,则值本身会受到保护,但引用标记不会成为价值的一部分,这可能会特别打扰连续;

  • use cd /D instead of cd in order to change also the drive if necessary; by the way, the interim variable dir is not necessary (let me recommend to not use variable names that equal internal or external commands for the sake of readability), simply use cd on the path immediately;
  • you missed using delayed expansion in line set pems[%pemI%]=%%~nxp, it should read set pems[!pemI!]=%%~nxp as pemI becomes updated within the surrounding for loop;
  • you either need delayed expansion in line set /a pemI=%pemI%+1 too, or you make use of the implicit variable expansion of set /A, so set /A pemI=pemI+1 would work; this can even be more simplified however: set /A pemI+=1, which is totally equivalent;
  • I would use case-insensitive comparison particularly when it comes to user input, like your check for Q, which would be if /I "!selectedPem!"=="q";
  • now we come to a line that needs what we have learned above: ECHO !pems[%selectedPem%]! needs to become call echo %%pems[!selectedPem!]%%; an alternative way using for is inserted too in a comment (rem);
  • then there is another line with the same problem: set privatekey=!pems[%selectedPem%]! needs to become call set privatekey=%%pems[!selectedPem!]%% (or the approach based on for too);
  • once again you missed using delayed expansion, this time in line ECHO You chose %privatekey%, which should read echo You chose !privatekey!;
  • finally I improved quotation of your code, in particular that of set commands, which should better be written like set "VAR=Value", so any invisible trailing white-spaces do not become part of the value, and the value itself becomes protected if it contains special characters, but the quotation marks do not become part of the value, which could disturb particularly for contatenation;

所以这是固定代码(全部你的原始 rem 删除了备注):

So here is the fixed code (with all your original rem remarks removed):

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

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

I have to admit I did not check the logic of your script in detail though.

这篇关于对IF语句中的数组使用DelayedExpansion索引变量失败的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆