如何将变量的值设置为批处理中另一个变量定义的数组槽的值(参见代码)

How do you set the value of a variable to the value of an arrays slot defined by another variable(see code) in Batch

在我尝试以批处理格式创建的较大程序中,重要的是我要学习如何将变量值设置为由另一个变量的数量确定的数组值(如果可能的话)简单的.bat 文件。我当前的测试文件代码为

    set a[1]=1
    set a[0]=10000
    set c=0
    set b=%a[%%c]% 
    echo %b%

所有这些 return 是: ECHO 已关闭 我需要它 return: 10000 这样我就可以使用类似的代码

    @echo off
    setlocal enabledelayedexpansion

    set Goblin[0]=5
    set Goblin[1]=20
    set Goblin[2]=7
    set Goblin[3]=Goblin

    set Zombie[0]=7
    set Zombie[1]=15
    set Zombie[2]=3
    set Zombie[3]=Zombie

    set Skeleton[0]=3
    set Skeleton[1]=11
    set Skeleton[2]=10
    set Skeleton[3]=Skeleton

    set Orc[0]=7
    set Orc[1]=25
    set Orc[2]=0
    set Orc[3]=Orc

    set Acrobat[0]=4
    set Acrobat[1]=15
    set Acrobat[2]=80
    set Acrobat[3]=Acrobat

    set PossibleEnemies[0]=!Goblin!
    set PossibleEnemies[1]=!Zombie!
    set PossibleEnemies[2]=!Skeleton!
    set PossibleEnemies[3]=!Orc!
    set PossibleEnemies[4]=!Acrobat!

    set /a Enemy1=%random% %%5
    set /a Enemy2=%random% %%5

    set /a Enemy1.MaximumHealth=%PossibleEnemies[Enemy1[1]]%
    set /a Enemy2.MaximumHealth=%PossibleEnemies[Enemy2[1]]%
    set /a Enemy1.Damage=%PossibleEnemies[Enemy1[0]]%
    set /a Enemy2.Damage=%PossibleEnemies[Enemy2[0]]%
    set /a Enemy1.Dodge=%PossibleEnemies[Enemy1[2]]%
    set /a Enemy2.Dodge=%PossibleEnemies[Enemy2[2]]%
    set Enemy1.Name=%PossibleEnemies[Enemy1[3]]%
    set Enemy2.Name=%PossibleEnemies[Enemy2[3]]%

随机化你正在与哪个敌人战斗(你一次打两个)。 我也试过

    set a[1]=1
    set a[0]=10000
    set c=0
    set b=%a[%c%]% 
    echo %b%

    set a[1]=1
    set a[0]=10000
    set c=0
    set b=%a[!c!]% 
    echo %b%

以及

    set a[1]=1
    set a[0]=10000
    set c=0
    set b=%a[c]% 
    echo %b%

无果

我相信还有其他技巧可以做到这一点,但基本方法是这样的:

@ECHO OFF
    SETLOCAL EnableDelayedExpansion
    set a[1]=1
    set a[0]=10000
    set c=0
    set b=!a[%c%]!
    echo %b%

编辑: Stephan 的评论有一个 link,其中包含我不记得的技巧,但我知道它使用了 CALL注意:调用会花费时间并且会减慢脚本速度。):

@ECHO OFF
    set a[1]=1
    set a[0]=10000
    set c=0
    CALL SET b=%%a[%c%]%%
    echo %b%

cmd/batch 中没有数组。唯一的变量类型是 STRING(尽管可以在一定程度上模拟列表和数组)。因此,我将从数组(每个元素的独立变量)更改为列表(一个包含所有元素的变量)并将所有“array/list stuff handling”放入子例程中,以便在主代码中轻松获取某个元素:

@echo off
setlocal enabledelayedexpansion

REM define readable names for indexes (for more readable code later)
set "MaxHealth=2"
set "Damage=1"
set "Dodge=3"
set "Name=4"
set "Goblin=1"
set "Zombie=2"
set "Skeleton=3"
set "Orc=4"
set "Acrobat=5"

REM define a list of properties for each creature
set "_Goblin=5,20,7,little Goblin"
set "_Zombie=7,15,3,angry Zombie"
set "_Skeleton=3,11,10,bony Skeleton"
set "_Orc=7,25,0,furious Orc"
set "_Acrobat=4,15,80,fast Acrobat"

REM define a list of all creatures
set "PossibleEnemies=Goblin,Zombie,Skeleton,Orc,Acrobat"

set /a Enemy1=%random% %%5
set /a Enemy2=%random% %%5

REM get properties
call :GetProperty %Enemy1% Name Enemy1
call :GetProperty %Enemy2% Name Enemy2
call :GetProperty %Enemy1% MaxHealth Enemy1
call :GetProperty %Enemy2% MaxHealth Enemy2
call :GetProperty %Enemy1% Damage Enemy1
call :GetProperty %Enemy2% Damage Enemy2
call :GetProperty %Enemy1% Dodge Enemy1
call :GetProperty %Enemy2% Dodge Enemy2

echo You fight against:
set Enemy
goto :eof

:GetProperty Type Property VarName  ; resulting variable = [VarName].[Property]
echo DBG: EnemyType=%1
echo DBG: Property=%2
for /f "tokens=%1 delims=," %%a in ("%PossibleEnemies%") do set "Type=%%a"
echo DBG: Type=%Type%
echo DBG: ReturnVar=%3.%2
set "opt=tokens=!%2! delims=," & REM not possible to use delayed expansion in for /f "[options]"
for /f "%opt%" %%a in ("!_%Type%!") do set "%3.%2=%%a"
echo DBG: Returnval=!%3.%2!
echo DBG: -----
goto :of

示例输出(没有 DBG 行):

You fight against:
Enemy1=3
Enemy1.Damage=3
Enemy1.Dodge=10
Enemy1.MaxHealth=11
Enemy1.Name=bony Skeleton
Enemy2=1
Enemy2.Damage=5
Enemy2.Dodge=7
Enemy2.MaxHealth=20
Enemy2.Name=little Goblin

如果需要,您可以将 REM get properties(所有这些 call ... 行)放入另一个子例程,以获得更具可读性的主代码。

要回答您的实际问题: 每个 depth-layer 需要一层扩展。你有一个变量包含一个变量包含一个变量,所以你需要三层解析(原始层加两层。多一层通常通过延迟扩展来实现;三层稍微复杂一点:使用call对于附加层。最里面的变量“照常”使用 %c% 用于下一层,您必须将每个 % 加倍,因为一个 % 被附加的解析层消耗(第二个call),最外层(第三)层(第一个call)也是如此,导致四个%,因为每层解析将它们减半:

@echo off
setlocal 
set a[2]=1000
set b[1]=2
set c=1
call call set d=%%%%a[%%b[%c%]%%]%%%%
echo %d%

结果:1000

关于效率的一些话:

正如@Darin 已经提到的,call 很慢(因为它创建了 cmd 的另一个实例。当然 call call 甚至更慢。看看时间三循环采用(一个空循环,一个“call-call”循环和一个使用另一个循环强制另一层解析的循环):

@echo off
setlocal 
set a[2]=1000
set b[1]=2
set c=1

echo start empty loop %time%
for /l %%i in (1,1,10000) do (
  REM
)
echo start call-call %time%
for /l %%i in (1,1,10000) do (
  call call set d=%%%%a[%%b[%c%]%%]%%%%
)
echo (proof it worked: %d%)
echo start speed-optimized %time%
setlocal enabledelayedexpansion

for /l %%i in (1,1,10000) do (
    for %%j in (!b[%c%]!) do set "x=!a[%%j]!"
)
echo (proof it worked: %x%)
endlocal
echo End %time% 

输出:

start empty loop 16:20:49,05
start call-call 16:20:49,06
(proof it worked: 1000)
start speed-optimized 16:21:02,51
(proof it worked: 1000)
End 16:21:03,06

(~0.1 秒/~12 秒/~0.6 秒) - YMMV 取决于硬件和处理器负载。好的,需要 10000 次迭代才能得到这些数字,但尽管如此 - 它是 ~20)

的一个因数

我发现使用数组是一个错误,我相信这可能是因为如果你像 a[0] 一样使用它们,那么它会将变量命名为 a[0] 而不是创建一个数组。所以我最终发现有更好的解决方案(至少对于我的问题)。

call set PossibleEnemies.Name.0=Goblin
call set PossibleEnemies.Name.1=Zombie
call set PossibleEnemies.Name.2=Skeleton
call set PossibleEnemies.Name.3=Orc
call set PossibleEnemies.Name.4=Acrobat

call set PossibleEnemies.Damage.0=5
call set PossibleEnemies.Damage.1=3
call set PossibleEnemies.Damage.2=3
call set PossibleEnemies.Damage.3=7
call set PossibleEnemies.Damage.4=4

call set PossibleEnemies.Health.0=20
call set PossibleEnemies.Health.1=15
call set PossibleEnemies.Health.2=11
call set PossibleEnemies.Health.3=25
call set PossibleEnemies.Health.4=15

call set PossibleEnemies.Dodge.0=7
call set PossibleEnemies.Dodge.1=3
call set PossibleEnemies.Dodge.2=10
call set PossibleEnemies.Dodge.3=0
call set PossibleEnemies.Dodge.4=80

:EnemySetup1
set /a Enemy1.Num=%random% %%5
set /a Enemy2.Num=%random% %%5
goto EnemySetup2

:EnemySetup2
call set "Enemy1.MaximumHealth=!PossibleEnemies.Health.%Enemy1.Num%!"
call set "Enemy2.MaximumHealth=!PossibleEnemies.Health.%Enemy2.Num%!"
call set "Enemy1.Health=!PossibleEnemies.Health.%Enemy1.Num%!"
call set "Enemy2.Health=!PossibleEnemies.Health.%Enemy2.Num%!"
call set "Enemy1.Damage=!PossibleEnemies.Damage.%Enemy1.Num%!"
call set "Enemy2.Damage=!PossibleEnemies.Damage.%Enemy2.Num%!"
call set "Enemy1.Dodge=!PossibleEnemies.Health.%Enemy1.Num%!"
call set "Enemy2.Dodge=!PossibleEnemies.Dodge.%Enemy2.Num%!"
call set Enemy1.Name=!PossibleEnemies.Name.%Enemy1.Num%!
call set Enemy2.Name=!PossibleEnemies.Name.%Enemy2.Num%!

是的,所以如果您需要使用数组,可能会有更好的选择。对我来说,它只是使用嵌套变量。因此,如果您是初学者,我建议您不惜一切代价避免使用变量!