windows cmd.exe(批处理)脚本中的数组、链表等数据结构

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/10166386/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-09 09:27:15  来源:igfitidea点击:

Arrays, linked lists and other data structures in cmd.exe (batch) script

arrayswindowsbatch-filecmddelayedvariableexpansion

提问by Aacini

I was playing with cmd.exe, but in its help I didn't find any info, how to define arrays.

我在玩 cmd.exe,但在它的帮助中我没有找到任何信息,如何定义数组。

I have found, how to define simple variables:

我发现,如何定义简单的变量:

set a=10
echo %a%

But, I want to create arrays, linked list etc...

但是,我想创建数组、链表等...

So, does it able in cmd.exe ( I mean: does in cmd.exe exist any array keywords? )

那么,它是否能够在 cmd.exe 中使用(我的意思是:在 cmd.exe 中是否存在任何数组关键字?)

I want to realize some algorithms as:

我想将一些算法实现为:

  • bubble sort
  • quick sort
  • gnome sort
  • 冒泡排序
  • 快速排序
  • 侏儒排序

etc...

等等...

So, I also want to know, does Cmd.exe have references or instances, structs etc?

所以,我也想知道,Cmd.exe 是否有引用或实例、结构等?

Cause its help not full in: /?

原因是它的帮助不是完整的:/?

Could Cmd.exe be defined as full by Turing-Machine definition? ( Turing-Complete )

Cmd.exe 可以被图灵机定义定义为完整的吗?(图灵完备)

回答by Aacini

Ok. I'll try to be as clear as possible to not be misunderstood...

好的。我会尽量说清楚,以免被误解......

In Windows Batch files a variable nameshould begin with a letter and may include any valid character, where valid charactersare: #$'()*+,-.?@[]_`{}~ besides letters and digits.

在 Windows 批处理文件中,变量名称应以字母开头,并且可以包含任何有效字符,其中有效字符是:#$'()*+,-.?@[]_`{}~ 除了字母和数字。

This means that from the cmd.exe point of view, SET NORMAL_NAME=123is exactly the same as SET A#$'()*+,-.?@[\]_{}~=123and also the same as SET VECTOR[1]=123; all three are normal variables. This way, it is up to youto write variable names in the form of array elements:

这意味着,从 cmd.exe 的角度来看,与;SET NORMAL_NAME=123完全相同SET A#$'()*+,-.?@[\]_{}~=123,也相同SET VECTOR[1]=123;这三个都是正常变量。这样,您就可以以数组元素的形式编写变量名:

set elem[1]=First element
set elem[2]=Second one
set elem[3]=The third one

This way, echo %elem[2]%will show Second one.

这样,echo %elem[2]%将显示Second one.

If you want to use another variableas index, you must know that the replacement of variables enclosed in percent symbols by their values is parsed from left to right; this means that:

如果你想使用另一个变量作为索引,你必须知道用它们的值替换百分号包围的变量是从左到右解析的;这意味着:

set i=2
echo %elem[%i%]%

doesn't give the desired result because it means: show the value of the elem[variable, followed by i, followed by the value of the ]variable.

没有给出想要的结果,因为它意味着:显示elem[变量的值,然后是i,然后是]变量的值。

To solve this problem you must use Delayed Expansion, that is, insert setlocal EnableDelayedExpansioncommand at the beginning, enclose index variables in percent symbols, and enclose the array elements in exclamation marks:

要解决这个问题,必须使用延迟扩展,即setlocal EnableDelayedExpansion在开头插入命令,将索引变量括在百分比符号中,并将数组元素括在感叹号中:

setlocal EnableDelayedExpansion
set elem[1]=First element
set elem[2]=Second one
set elem[3]=The third one
set i=2
echo !elem[%i%]!

You may also use parameters of FOR commands as indexes: for /L %%i in (1,1,3) do echo !elem[%%i]!. You must use !index! to store values in array elements when the index is changed inside a FOR or IF: set elem[!index!]=New value. To get the value of an element when the index changes inside FOR/IF, enclose the element in double percent symbols and precede the command with call. For example, to move a range of array elements four places to the left:

您还可以使用 FOR 命令的参数作为索引:for /L %%i in (1,1,3) do echo !elem[%%i]!. 您必须使用 !index! 当在 FOR 或 IF: 中更改索引时将值存储在数组元素中set elem[!index!]=New value。要在 FOR/IF 内的索引更改时获取元素的值,请将元素括在双百分号中并在命令前加上call. 例如,要将一系列数组元素向左移动四位:

for /L %%i in (%start%,1,%end%) do (
   set /A j=%%i + 4
   call set elem[%%i]=%%elem[!j!]%%
)

Another way to achieve the previous process is to use an additional FOR command to change the delayed expansion of the index by an equivalent replaceable parameter, and then use the delayed expansion for the array element. This method runs faster than previous CALL:

实现前面过程的另一种方法是使用额外的 FOR 命令将索引的延迟扩展更改为等效的可替换参数,然后对数组元素使用延迟扩展。此方法比之前的 CALL 运行得更快:

for /L %%i in (%start%,1,%end%) do (
   set /A j=%%i + 4
   for %%j in (!j!) do set elem[%%i]=!elem[%%j]!
)

This way, the Batch file behaveslike it manages arrays. I think the important point here is not to discuss if Batch manages arrays or not, but the fact that you may manage arrays in Batch files in an equivalent way of other programming languages.

这样,批处理文件的行为就像管理数组一样。我认为这里的重点不是讨论 Batch 是否管理数组,而是您可以以与其他编程语言等效的方式管理 Batch 文件中的数组这一事实。

@echo off
setlocal EnableDelayedExpansion

rem Create vector with names of days
set i=0
for %%d in (Sunday Monday Tuesday Wednesday Thrusday Friday Saturday) do (
   set /A i=i+1
   set day[!i!]=%%d
)

rem Get current date and calculate DayOfWeek
for /F "tokens=1-3 delims=/" %%a in ("%date%") do (
   set /A mm=10%%a %% 100, dd=10%%b %% 100, yy=%%c
)
if %mm% lss 3 set /A mm=mm+12, yy=yy-1
set /A a=yy/100, b=a/4, c=2-a+b, e=36525*(yy+4716)/100, f=306*(mm+1)/10, jdn=c+dd+e+f-1523, dow=jdn %% 7 + 1
echo Today is !day[%dow%]!, %date%

Note that index values are not limited to numbers, but they may be any string that contain valid characters; this point allows to define what in other programming languages are called associative arrays. At this answerthere is a detailed explanation of the method used to solve a problem using an associative array. Note also that the space is a valid character in variable names, so you must pay attention to not insert spaces in variable names that may go unnoticed.

请注意,索引值不限于数字,它们可以是任何包含有效字符的字符串;这一点允许定义在其他编程语言中称为关联数组的内容。在这个答案中,详细解释了使用关联数组解决问题的方法。另请注意,空格是变量名中的有效字符,因此您必须注意不要在变量名中插入可能被忽视的空格。

I elaborated on the reasons I have to use array notation in Batch files at this post.

我在这篇文章中详细说明了必须在批处理文件中使用数组表示法的原因。

In this postthere is a Batch file that reads a text file and stores the indexes of the lines in a vector, then does a Buble Sort of vector elements based on line contents; the equivalent result is a sort over file contents.

这篇文章中有一个批处理文件,它读取文本文件并将行的索引存储在向量中,然后根据行内容对向量元素进行 Buble 排序;等效的结果是对文件内容进行排序。

In this postthere is a basic Relational Data Base application in Batch based on indexes stored in files.

这篇文章中,有一个基于文件中存储的索引的 Batch 中的基本关系数据库应用程序。

In this postthere is a complete multiple linked-list application in Batch that assembles a large data structure taken from a subdirectory and displays it in the form of TREE command.

这篇文章中,有一个完整的 Batch 多链表应用程序,它组装了一个从子目录中获取的大型数据结构,并以 TREE 命令的形式显示它。

回答by trutheality

Windows shell scripting really isn't designed to work with arrays, let alone complex data structures. For the most part, everything's a string in the windows shell, but, there are some things you can do to "work with" arrays, like declaring nvariables VAR_1, VAR_2, VAR_3...using a loop and filtering on the prefix VAR_, or creating a delimited string and then using the FORconstruct that iterates over a delimited string.

Windows shell 脚本实际上并不是为处理数组而设计的,更不用说复杂的数据结构了。在大多数情况下,windows shell 中的一切都是一个字符串,但是,您可以做一些事情来“处理”数组,例如使用循环声明n变量VAR_1, VAR_2, VAR_3...并过滤前缀VAR_,或者创建一个分隔字符串,然后使用FOR迭代分隔字符串的构造。

Similarly, you can use the same basic idea to create a struct-like set of variables like ITEM_NAME, ITEM_DATAor w/e. I even found this linkthat talks about simulating an associative array in CMD.

同样,您可以使用相同的基本思想来创建一组类似结构的变量,例如ITEM_NAME, ITEM_DATA或 w/e。我什至发现这个链接讨论了在 CMD 中模拟关联数组。

It is all terribly hackish and inconvenient when it comes down to it. The command-line shell just wasn't designed for heavy programming. I agree with @MatteoItalia -- if you need serious scripting, use a real scripting language.

归根结底,这一切都非常骇人听闻且不方便。命令行 shell 并不是为繁重的编程而设计的。我同意@MatteoItalia——如果您需要认真的脚本编写,请使用真正的脚本语言。

回答by Dave_J

I made a bubble sort implementation in batch using pseudo-arrays a while ago. Not sure why you'd use it (although I will admit to doing so in another batch file) as it gets pretty slow as the list size increases. It was more to set myself a little challenge. Someonemight find this useful.

不久前,我使用伪数组批量实现了冒泡排序。不知道为什么要使用它(尽管我承认在另一个批处理文件中这样做),因为随着列表大小的增加它变得非常慢。更像是给自己一个小小的挑战。 有人可能会发现这很有用。

:: Bubblesort
:: Horribly inefficient for large lists
:: Dave Johnson implementation 05/04/2013
@echo off
setlocal enabledelayedexpansion
:: Number of entries to populate and sort
set maxvalue=50
:: Fill a list of vars with Random numbers and print them
for /l %%a in (1,1,%maxvalue%) do (
    set /a tosort%%a=!random!
)
:: echo them
set tosort
:: Commence bubble sort
Echo Sorting...
set /a maxvalue-=1
set iterations=0
for /l %%a in (%maxvalue%,-1,1) do ( REM Decrease by 1 the number of checks each time as the top value will always float to the end
    set hasswapped=0
        for /l %%b in (1,1,%%a) do (
            set /a next=%%b+1
            set next=tosort!next!
            set next=!next!
            call :grabvalues tosort%%b !next!
            rem echo comparing tosort%%b = !tosortvalue! and !next! = !nextvalue!
            if !nextvalue! LSS !tosortvalue! (
            rem set /a num_of_swaps+=1
            rem echo Swapping !num_of_swaps!
                set !next!=!tosortvalue!
                set tosort%%b=!nextvalue!
                set /a hasswapped+=1
            )
        )
    set /a iterations+=1
    if !hasswapped!==0 goto sorted
)
goto:eof
:grabvalues
set tosortvalue=!%1!
set nextvalue=!%2!
goto:eof
:sorted
::nice one our kid
set tosortvalue=
echo Iterations required: %iterations%
set tosort
endlocal

回答by aschipfl

Concerning this statement:

关于此声明:

I have found, how to define simple variables:

set a = 10
echo %a%

我发现,如何定义简单的变量:

set a = 10
echo %a%

This is simply wrong! Variable awill remain empty (supposing it was empty initially) and echo %a%will return ECHO is on.A variable called aSPACEwill actually be set to the value SPACE10.

这简直是​​错误的!变量a将保持为空(假设它最初是空的)并echo %a%返回ECHO is on.一个被调用的变量aSPACE实际上将被设置为值SPACE10

So for the code to work, you must get rid of the SPACEsaround the equal-to sign:

因此,要使代码正常工作,您必须去掉SPACEs等号周围的内容:

set a=10
echo %a%

To make the assignment safe against all characters, use the quoted syntax (supposing you have the command extensionsenabled, which is the default for the Windows command prompt anyway):

为了使分配对所有字符安全,请使用带引号的语法(假设您启用了命令扩展,无论如何这是 Windows 命令提示符的默认值):

set "a=1&0"
echo(%a%

For all the rest of your question I recommend to read Aacini's great and comprehensive answer.

对于您的所有其他问题,我建议您阅读Aacini出色而全面的回答

回答by Matteo Italia

Seriously speaking: I never heard that batch has arrays, maybe you can emulate them with some strange trick, but I wouldn't call it a good idea.

说真的:我从来没有听说过批处理有数组,也许你可以用一些奇怪的技巧来模拟它们,但我不会称之为一个好主意。

References/instances/structs are stuff for a real language, cmd scripting is just a bunch of extensions that grew over the very primitive interpreter that was command.com, you can do some basic scripting, but anything more complicated than a bunch of calls to other commands is doomed to become ugly and incomprehensible.

引用/实例/结构是真正语言的东西,cmd 脚本只是在非常原始的解释器 command.com 上增长的一堆扩展,你可以做一些基本的脚本,但比一堆调用更复杂的东西其他命令注定会变得丑陋和难以理解。

The only "advanced" construct is the do-it-all weirdo forloop, which, mixed with the strange "rules" of variable substitution (%var%, %%var, !var!, are different stuff because of the idiotic parser), makes writing even trivial algorithms a collection of strange hacks (see e.g. here for an implementation of quicksort).

唯一的“高级”构造是全能的怪人for循环,它与变量替换的奇怪“规则”(%var%, %%var, !var!, 由于愚蠢的解析器而不同)混合在一起,使得编写甚至是微不足道的算法成为奇怪的技巧(参见例如这里的 quicksort 实现)。

My tip is, if you want to do your scripting in a sane way, use a realscripting language, and leave batch for simple, quick hacks and for backwards compatibility.

我的建议是,如果您想以理智的方式编写脚本,请使用真正的脚本语言,并保留批处理以进行简单、快速的黑客攻击和向后兼容。

回答by Ben Personick

TLDR:

域名注册地址:

I hit upon the Idea of using a "For" loop and the "set" command to allow the parsing of variables, allowing me to create Pseudo Arrays, both ordered and linked-list style, and more importantly, Pseudo Objects akin to structures.

我想到了使用“For”循环和“set”命令来解析变量的想法,允许我创建伪数组,有序和链表样式,更重要的是,类似于结构的伪对象。

A typical batch Pseudo Array, and how to parse:

一个典型的批处理伪数组,以及如何解析:

SET "_Arr.Names="Name 1" "Name 2" ... "Name N""

FOR %A IN (%_Arr.Names%) DO @( Echo.%~A )

REM Results:

REM Name 1
REM Name 2
REM ...
REM Name N

Below we make some Dumb Pseudo Arrays and a manual ordered Pseudo Array, plus create an Ordered Pseudo Array catching the output of a DIR Command.

下面我们制作一些哑伪数组和一个手动排序的伪数组,并创建一个有序伪数组来捕获 DIR 命令的输出。

We also take the Dumb Pseudo Arrays and convert them into Ordered arrays (removing the original Dumb Pseudo Array variables after).

我们还采用哑伪数组并将它们转换为有序数组(之后删除原始哑伪数组变量)。

We then update all of the ordered Arrays to contain more elements manually.

然后我们手动更新所有有序数组以包含更多元素。

Finally we Dynamically report some of the values from the Array by doing a pre-defined For L Loop for values 7 to 9, and Generating a Random value to print the 4th example value of the array.

最后,我们通过对值 7 到 9 执行预定义的 For L 循环并生成随机值以打印数组的第 4 个示例值,动态报告数组中的一些值。

Note:

笔记:

I create a variable to hold the method for adding members to make adding them simpler.

我创建了一个变量来保存添加成员的方法,以使添加它们更简单。

I point this out as it should make it easy to see how we make the minor jump from ordered arrays to Pseudo objects.

我指出这一点是因为它应该可以很容易地看出我们如何从有序数组到伪对象的小跳转。

@(
 SETLOCAL ENABLEDELAYEDEXPANSION
 ECHO OFF

 REM Manually Create a shortcut method to add more elements to a specific ordered array
 SET "_Arr.Songs.Add=SET /A "_Arr.Songs.0+=1"&&CALL SET "_Arr.Songs.%%_Arr.Songs.0%%"

 REM Define some 'dumb' Pseudo arrays
 SET "_Arr.Names="Name 1" "Name 2" "Name 3" "Name 4" "Name 5" "Name 6" "Name 7" "Name 8""
 SET "_Arr.States="AL" "AK" "AZ" "AR" "CA" "CO" "CT" "DE" "FL" "GA" "HI" "ID" "IL" "IN" "IA" "KS" "KY" "LA" "ME" "MD" "MA" "MI" "MN" "MS" "MO" "MT" "NE" "NV" "NH" "NJ" "NM" "NY" "NC" "ND" "OH" "OK" "OR" "PA" "RI" "SC" "SD" "TN" "TX" "UT" "VT" "VA" "WA" "WV" "WI" "WY""

)

REM Manually Create One Ordered Array
%_Arr.Songs.Add%=Hey Jude"
%_Arr.Songs.Add%=The Bartman"
%_Arr.Songs.Add%=Teenage Dirtbag"
%_Arr.Songs.Add%=Roundabout"
%_Arr.Songs.Add%=The Sound of Silence"
%_Arr.Songs.Add%=Hyman and Diane"
%_Arr.Songs.Add%=One Angry Dwarf and 200 Solumn Faces"

REM Turn All Pre-Existing Normal Pseudo Arrays into Element Arrays
REM Since Ordered Arrays use Index 0, we can skip any manually created Ordered Arrays:
FOR /F "Tokens=2 Delims==." %%A IN ('SET _Arr. ^| FIND /V ".0=" ^| SORT') DO (
 IF /I "%%~A" NEQ "!_TmpArrName!" (
  SET "_TmpArrName=%%~A"
  IF NOT DEFINED _Arr.!_TmpArrName!.Add (
   REM Create a shortcut method to add more members to the array
   SET "_Arr.!_TmpArrName!.Add=SET /A "_Arr.!_TmpArrName!.0+=1"&&CALL SET "_Arr.!_TmpArrName!.%%_Arr.!_TmpArrName!.0%%"
  )
  FOR %%a IN (!_Arr.%%~A!) DO (
   CALL SET /A "_Arr.!_TmpArrName!.0+=1"
   CALL SET "_Arr.!_TmpArrName!.%%_Arr.!_TmpArrName!.0%%=%%~a"
  )
 )
 IF DEFINED _Arr.!_TmpArrName! (
  REM Remove Unneeded Dumb Psuedo Array "_Arr.!_TmpArrName!"
  SET "_Arr.!_TmpArrName!="
 )
)

REM Create New Array of unknown Length from Command Output, and Store it as an Ordered Array
 SET "_TmpArrName=WinDir"
 FOR /F "Tokens=* Delims==." %%A IN ('Dir /B /A:D "C:\Windows"') DO (
  IF NOT DEFINED _Arr.!_TmpArrName!.Add (
   SET "_Arr.!_TmpArrName!.Add=SET /A "_Arr.!_TmpArrName!.0+=1"&&CALL SET "_Arr.!_TmpArrName!.%%_Arr.!_TmpArrName!.0%%"
  )
  CALL SET /A "_Arr.!_TmpArrName!.0+=1"
  CALL SET "_Arr.!_TmpArrName!.%%_Arr.!_TmpArrName!.0%%=%%~A"
 )
)

REM Manually Add additional Elements to the Ordered Arrays:
%_Arr.Names.Add%=Manual Name 1"
%_Arr.Names.Add%=Manual Name 2"
%_Arr.Names.Add%=Manual Name 3"

%_Arr.States.Add%=51st State"
%_Arr.States.Add%=52nd State"
%_Arr.States.Add%=53rd State"

%_Arr.Songs.Add%=Live and Let Die"
%_Arr.Songs.Add%=Baby Shark"
%_Arr.Songs.Add%=Safety Dance"

%_Arr.WinDir.Add%=Fake_Folder 1"
%_Arr.WinDir.Add%=Fake_Folder 2"
%_Arr.WinDir.Add%=Fake_Folder 3"

REM Test Output:

REM Use a For Loop to List Values 7 to 9 of each array and A Psuedo Rnadom 4th value
REM We are only interested in Ordered Arrays, so the .0 works nicely to locate those exclusively.
FOR /F "Tokens=2,4 Delims==." %%A IN ('SET _Arr. ^| FIND ".0=" ^| SORT') DO (
 CALL :Get-Rnd %%~B
 ECHO.
 ECHO.%%~A 7 to 9, Plus !_Rnd#! - Psuedo Randomly Selected
 FOR /L %%L IN (7,1,9) DO (
  CALL Echo. * Element [%%L] of %%~A Pseudo Array = "%%_Arr.%%~A.%%L%%"
 )
 CALL Echo. * Random Element [!_Rnd#!] of %%~A Pseudo Array = "%%_Arr.%%~A.!_Rnd#!%%"
)
ENDLOCAL 
GOTO :EOF

:Get-Rnd
 SET /A "_RandMax=(32767 - ( ( ( 32767 %% %~1 ) + 1 ) %% %~1) )", "_Rnd#=!Random!"
 IF /I !_Rnd#! GTR !_RandMax! ( GOTO :Get_Rnd# )
 SET /A "_Rnd#%%=%~1"
GOTO :EOF

Example Results:

示例结果:

Results:

Names 7 to 9, Plus 5 - Psuedo Randomly Selected
 * Element [7] of Names Pseudo Array = "Name 7"
 * Element [8] of Names Pseudo Array = "Name 8"
 * Element [9] of Names Pseudo Array = "Manual Name 1"
 * Random Element [5] of Names Pseudo Array = "Name 5"

Songs 7 to 9, Plus 5 - Psuedo Randomly Selected
 * Element [7] of Songs Pseudo Array = "One Angry Dwarf and 200 Solumn Faces"
 * Element [8] of Songs Pseudo Array = "Live and Let Die"
 * Element [9] of Songs Pseudo Array = "Baby Shark"
 * Random Element [5] of Songs Pseudo Array = "The Sound of Silence"

States 7 to 9, Plus 9 - Psuedo Randomly Selected
 * Element [7] of States Pseudo Array = "CT"
 * Element [8] of States Pseudo Array = "DE"
 * Element [9] of States Pseudo Array = "FL"
 * Random Element [9] of States Pseudo Array = "FL"

WinDir 7 to 9, Plus 26 - Psuedo Randomly Selected
 * Element [7] of WinDir Pseudo Array = "assembly"
 * Element [8] of WinDir Pseudo Array = "AUInstallAgent"
 * Element [9] of WinDir Pseudo Array = "Boot"
 * Random Element [26] of WinDir Pseudo Array = "Fonts"


Initially I would do things similar to Aacini, a simple line of variables with an incremental counter, manually, or assigning them through a simple loop from a quick list of variables.

最初我会做类似于 Aacini 的事情,一个简单的带有增量计数器的变量行,手动,或者通过一个简单的循环从变量的快速列表中分配它们。

This was fine for small 2-D Arrays.

这对于小型二维数组来说很好。

However I find it a pain for long arrays of data, especially when I need multi-value content.

但是,我发现长数据数组很痛苦,尤其是当我需要多值内容时。

To say nothing of when I need to match and populate content in those multi-dimensional arrays dynamically, where the simple usage there breaks down.

更不用说我何时需要动态匹配和填充这些多维数组中的内容,那里的简单用法就失效了。

I found that it became hard when you ended up needing multiple arrays of information which you needed to update or add features to across the board.

我发现当您最终需要多个信息数组时,您需要全面更新或添加功能,这变得很困难。

As such an array is essentially a list of sub-strings you need to exports as variables, and adding or changing their ordering means changing your code.

因此,数组本质上是您需要作为变量导出的子字符串列表,添加或更改它们的顺序意味着更改您的代码。

Take for instance a scenario where you need to log into multiple FTP servers, delete files older than X days from certain paths.

以需要登录多个 FTP 服务器,从某些路径删除超过 X 天的文件的场景为例。

Initially you might create simple arrays of substrings I'll define like this:

最初,您可能会创建简单的子字符串数组,我将像这样定义:

Site.##=[Array (String)] [Array (String)] @(
       IP=[SubSting],
       Username=[SubString],
       Password[SubString])

Or as shown in in this example code.

或者如本示例代码所示。

(
  SETOCAL
  ECHO OFF

  REM Manage Sites:
  SET "Sites=13"
  SET "MaxAge=28"

  SET "Site.1="[IP]" "[User Name]" "[Password]" "[Path]""
  SET "Site.2="[IP]" "[User Name]" "[Password]" "[Path]""
  SET "Site.3="[IP]" "[User Name]" "[Password]" "[Path]""
  REM  ...
  SET "Site.11="[IP]" "[User Name]" "[Password]" "[Path]""
  SET "Site.12="[IP]" "[User Name]" "[Password]" "[Path]""
  SET "Site.13="[IP]" "[User Name]" "[Password]" "[Path]""
)

FOR /L %%L IN (1,1,%Sites%) DO (
   FOR /F "Tokens=*" %%A IN ('CALL ECHO %%Site.%%L%%') DO (
      Echo. Pulled this example from a more complex example of my actual code, so the example variables may not need this loop, but it won't hurt to have if they don't need the extra expansion.
     Call :Log
     CALL :DeleteFTP %%~A
   )
)

GOTO :EOF
:DeleteFTP
   REM Simple ftp command for cygwin to delete the files found older than X days.
   SET "FTPCMD="%~dp0lftp" %~1 -u %~2,%~3 -e "rm -rf %~4%MaxAge% "
   FOR /F "Tokens=*" %%F IN ('"%FTPCMD% 2^>^&1"') DO @(
     ECHO.%%~F
   )
GOTO :EOF

Now, 13 sites, this isn't all that bad, I'm sure you're saying. right? You can just add one at the end and then put in the info and done.

现在,13 个站点,这还不算太糟糕,我敢肯定您是在说。对?您可以在最后添加一个,然后输入信息并完成。

Then you need to add the names of the sites in for reporting, so you add another term to each string at place 5 so you don't have to change your function..

然后您需要添加站点的名称以进行报告,因此您在第 5 位的每个字符串中添加另一个术语,这样您就不必更改您的功能。

::...
SET "Site.1="[IP]" "[User Name]" "[Password]" "[Path]" "[Site Name]""
::...

Then you realise you'll need to keep them in order by their site names (or IPs, but the names are easier for most people to remember and you need to be able to let other people have a look) so you change the order in all 13 spots, the call to expand the variables, and the function.

然后您意识到您需要按站点名称(或 IP,但名称对大多数人来说更容易记住,并且您需要能够让其他人查看)按顺序排列它们,因此您更改了所有 13 个点、扩展变量的调用和函数。

::...
SET "Site.1="[Site Name]" "[IP]" "[User Name]" "[Password]" "[Path]""
::...
FOR /F "Tokens=*" %%A IN ('CALL ECHO %%Site.%%L%%')
::...
SET "FTPCMD="%~dp0lftp" %~2 -u %~3,%~4 -e "rm -rf %~5%MaxAge% "
::...

Then it just keeps getting worse:

然后它只会变得更糟:

  • The number of directories you have to check, using different users, at the same site begins increasing.
  • You realise you need to have different retention times per site, and later, per directory.
  • You end up having 30, 40,50 of these and it's hard to remember which is which by looking at the end of a long string and copying them around, etc.

  • You stopped adding more paths, but sometime you have to remove old ones or it causes problems when they are gone, and if you forget to update the total number of site sin the list you might miss running the script on some.

  • when a directory is added or removed you have to add it/remove it fro each site making it harder to use the ordering, and easier to miss sites as they aren;t easy to ID.

  • 您必须使用不同用户在同一站点检查的目录数量开始增加。
  • 您意识到您需要为每个站点设置不同的保留时间,然后是每个目录。
  • 您最终会得到 30、40、50 个这样的字符串,并且很难通过查看长字符串的末尾并复制它们等来记住哪个是哪个。

  • 您停止添加更多路径,但有时您必须删除旧路径,否则会在它们消失时导致问题,并且如果您忘记更新列表中的站点总数,您可能会错过在某些上运行脚本。

  • 当添加或删除目录时,您必须从每个站点添加/删除它,这使得使用排序变得更加困难,并且更容易错过站点,因为它们不容易识别。

Just, what a pain, and this isn't even when you need to have a dynamic set of objects, this is all manual.

只是,多么痛苦,甚至当您需要拥有一组动态对象时,这都不是,这都是手动的。

So what can you do? Well, here's what I did:

所以,你可以做什么?好吧,这就是我所做的:

I ended up resorting to implementing a sort of poor-mans Structure or Object-array (of strings) in my cmd scripts where the need fits.

我最终诉诸于在我的 cmd 脚本中实现一种适合需要的穷人结构或对象数组(字符串)。

IE the structure would be a "Site Object" which would have multiple properties, which might be objects with sub properties themselves. Since CMD is not actually Object oriented, its a bit of a kludge, just as arrays are.

IE 结构将是一个“站点对象”,它将具有多个属性,这些属性本身可能是具有子属性的对象。由于 CMD 实际上不是面向对象的,因此它有点杂乱,就像数组一样。

Since the example I started with ended up being the first place I tried these you can see this intermediate amalgam step I'll define like this:

由于我开始的示例最终成为我尝试这些的第一个地方,因此您可以看到这个中间汞合金步骤,我将这样定义:

eg: Site.[ID].[Object Property]=[Value, or array of values]

   Site
     .ID=[int]
      .Name=[string]
      .Path=[String]
      .MaxAge=[Int]
      .Details=[Array (String)] @(
       IP=[SubSting],
       Username=[SubString],
       Password[SubString])

To combat the issue with needing to re-order sets of Data on the fly I considered using a form of linked lists I toyed with, but since I wanted to easily add items to each grouping of sites while retaining order between sites I settled on a simple method.

为了解决需要即时重新排序数据集的问题,我考虑使用一种我玩过的链接列表形式,但由于我想轻松地将项目添加到每个站点分组,同时保持站点之间的顺序,我决定使用简单的方法。

Here is another code example of this step in usage:

这是使用此步骤的另一个代码示例:

@(
    SETLOCAL ENABLEDELAYEDEXPANSION
    ECHO OFF

    SET "_SiteCount=0"
    SET "_SiteID=0"

    SET /A "_SiteID= !_SiteID! + 1"
    SET "Site.!_SiteID!.MaxAge=Day5Ago"
    SET "Site.!_SiteID!.Name=[SITE NAME HEADER FOR EMAIL]"
    SET "Site.!_SiteID!.Detail="[IP]" "[UserName]" "[Password]" "[Path]""

    REM ...

    SET /A "_SiteID= !_SiteID! + 1"
    SET "Site.!_SiteID!.MaxAge=Day15Ago"
    SET "Site.!_SiteID!.Name=[SITE NAME HEADER FOR EMAIL]"
    SET "Site.!_SiteID!.Detail="[IP]" "[UserName]" "[Password]" "[Path]""
)

CALL :Main

(
    ENDLOCAL
    Exit /b %eLvl%
)

:Main
   REM In some forms of these the order isn't meaningful, but in others you need to follows the order and so we just count he number of site objects by counting one of their properties.
   FOR /F %%A IN ('SET ^| FIND /I "Site." ^| FIND /I ".Name="') DO ( CALL SET /A "_SiteCount+=1" )
    FOR /L %%L IN (1,1,34) DO (
        CALL :PSGetDate_DaysAgo %%L
    )
    FOR /L %%L IN (1,1,%_SiteCount%) DO (
        SET "Site.%%L.Create=NONE"
    )
    FOR /L %%L IN (1,1,%_SiteCount%) DO (
        FOR /F "Tokens=*" %%A IN ('CALL ECHO ""%%Site.%%L.Name%%" %%Site.%%L.Detail%% "Site.%%L" "%%%%Site.%%L.MaxAge%%%%""') DO (
            CALL ECHO CALL :DeleteFTP %%~A
            CALL :DeleteFTP %%~A
        )
    )
    CALL :SendMail "%EMLog%" "%_EMSubject%"

GOTO :EOF

:DeleteFTP
    REM ECHO.IF "%~7" EQU "%skip%" (
    IF "%~7" EQU "%skip%" (
        GOTO :EOF
    )
    SET "FTPCMD="%~dp0lftp" %~2 -u %~3,%~4 -e "rm -rf %~5%~7 "
    SET "FTPCMD=%FTPCMD%; bye""
    FOR /F "Tokens=*" %%F IN ('"%FTPCMD% 2^>^&1"') DO @(
        ECHO."%%F"
        ECHO."%%~F"
        REM CALL :Output "%Temp%\%~2_%~7.log" "%%F"
        %OP% "%Temp%\%~2_%~7.log"
        SET "FTPOut=%%~F"
    )
GOTO :EOF

As you can probably see, these structures work very well where you have sets of forking hierarchical data that you need to apply manually and show data in a specific sequential order.

您可能会看到,这些结构在您需要手动应用多组分叉分层数据并按特定顺序显示数据的情况下非常有效。

Although, to be sure today I usually make the base of the structures the name of the script, as I find this is more useful, and may or may not use ordered arrays depending on need.

虽然,可以肯定的是,今天我通常将结构的基础作为脚本的名称,因为我发现这更有用,并且可能会或可能不会根据需要使用有序数组。

SET "_GUID=^%Time^%_^%Random:~-1^%^%Random:~-1^%^%Random:~-1^%^%Random:~-1^%^%Random:~-1^%^%Random:~-1^%^%Random:~-1^%^%Random:~-1^%^%Random:~-1^%"

eg: %~n0.[ObjectName].[Object Property].[Object Sub Property]=[Value, or array of values]

       [Script Name]
         .[Object Name](May Hold Count of Names)=[int]
          .Name=[string]
          .Paths(May Hold Count of IDs)=[INT]
            .GUID=%_GUID%
             .Path=String
             .MaxAge=[Int]
          .Details=[Array (String)] @(
           IP=[SubSting],
           Username=[SubString],
           Password[SubString])

But what about where you might have to collect large sets of dynamically generated data, and group it into pre-made categories and then mix that up to report it.

但是,您可能需要在哪里收集大量动态生成的数据,并将其分组到预先制定的类别中,然后将其混合起来进行报告呢?

Well here again these can be useful too, and you can build them on the fly in your code adding more properties as needed.

在这里,这些也很有用,您可以在代码中动态构建它们,根据需要添加更多属性。

In a similar script to the FTP delete, we need to check the sizes of multiple directories, I am going to dumb tis one down quite a bit and look at just one check:

在一个与 FTP 删除类似的脚本中,我们需要检查多个目录的大小,我将把它放低很多,只看一个检查:

@(
    SETLOCAL ENABLEDELAYEDEXPANSION
    ECHO OFF

    SET /A "_SiteID= !_SiteID! + 1"
    SET "SiteName=SiteA"
    SET "%~n0.!SiteName!=%%_SiteID%%
    SET "%~n0.!SiteName!.SiteID=!_SiteID!
    SET "%~n0.!SiteName!.Paths="PathA" "PathB" "PathC" "PathD" "PathE""
)

CALL :CheckFTP [FTP Login variables from source object including Site ID]

:CheckFTP
 REM Not necessary to assign Variables, doing this for exposition only:
 CALL SET "TempSiteName=%~6"
 CALL SET "TempPaths=%%%~n0.%~1.Paths%%"
 REM Clear the site Temp KB variables
 FOR \F "Tokens=2* Delims== " %%H IN (%TempPaths% "Total" "Temp") DO (
  CALL SET /A "%%%~n0.%~1.Paths.%%~H.KB=0"
 )
 FOR %%J IN (%TempPaths%) DO (
   FOR /F "Tokens=1-2" %%F IN ('[FTP Command using source object options]') DO @(
     CALL :SumSite "%~6" "%%~F" "%%~G"
     FOR /F "Tokens=1,2,* delims=/" %%f IN ("%%~G") DO (
       CALL :ConvertFolder "%~6" "%%~F" "%%~g" "%%~h" "%~6_%%~g_%%~h"
     )
   )
 )

FOR /F "Tokens=3,4,7 Delims==_." %%g IN ('SET ^| FIND /I "%~6_" ^| FIND /I ".KB" ^| FIND /I /V "_."') DO (
    CALL :WriteFolder "%%g/%%~h" "%TmpFile%" "%~6_%%~g_%%~h"
    REM echo.CALL :WriteFolder "%%g/%%~h" "%TmpFile%" "%~6_%%~g_%%~h"
)
CALL :ConvertSite "%~1"
CALL :WriteTotalFolder "%~7" "%TmpFile%" "%~6"
CALL :SendMail "%TmpFile%" "Backup_%~1"
GOTO :EOF

:SumSite
  CALL SET "TSumPaths=%%%~n0.%~1.Paths%% "Total""
   FOR %%H IN (%TSumPaths%) DO (
    CALL SET /A "%~n0.%~1.Paths.%%~H.KB=%%%~n0.%~1.Paths.%%~H.KB%%+%~2"
  )

:SumSite
  CALL SET "TSumPaths=%%%~n0.%~1.Paths%% "Total""
   FOR %%H IN (%TSumPaths%) DO (
    CALL SET /A "%~n0.%~1.Paths.%%~H.KB=%%%~n0.%~1.Paths.%%~H.KB%%+%~2"
  )
GOTO :EOF

:ConvertFolder
    REM Convert's Folder values to MB and GB
    SET /A "%~1.Temp.KB=%~2"
    CALL SET /A "%~1.Temp.MB=%%%~1.Temp.KB%%/1024"
    CALL SET /A "%~1.Temp.GB=(%%%~1.Temp.KB%%/1024)/1024"
    CALL SET /A "%~5.Temp.KB=%%%~5.Temp.KB%%+%~2"
    CALL SET /A "%~5.Temp.MB=%%%~5.Temp.KB%%/1024"
    CALL SET /A "%~5.Temp.GB=(%%%~5.Temp.KB%%/1024)/1024"
GOTO :EOF

:WriteFolder

    CALL :PickGMKBytes "%~1" "%~2" "G" "M" "K" "%%%~3.Temp.GB%%" "%%%~3.Temp.MB%%" "%%%~3.Temp.KB%%"

GOTO :EOF

:PickGMKBytes

    IF /I "%~6" NEQ "" (
        IF /I "%~6"=="0" (
            CALL :PickGMKBytes "%~1" "%~2" "%~4" "%~5" "%~6" "%~7" "%~8"
        ) ELSE (
            CALL :Output "%~2" "%~6%~3  %~1"
        )
    ) ELSE (
        CALL :Output "%~2" "0B  %~1"
    )

GOTO :EOF


:ConvertSite
 CALL SET "TempPaths=%%%~n0.%~1.Paths%%"
    FOR %%V IN (%TempPaths% "Total") DO (
        CALL SET /A "%~1.%%~V.MB=%%%~1.%%~V.KB%%/1024"
        CALL SET /A "%~1.%%~V.GB=(%%%~1.%%~V.KB%%/1024)/1024"
    )

GOTO :EOF

To be fair, this script example may not be very explicit in showing what is happening, and I had to make changes on the fly to fix a new object style, but essentially: It creates connection objects, and then dynamically extends them to include sub folders, and maintain running totals for each subfolder and site in KB, MB, and GB, and pics which of the values to report after summing up all of the directories for a given folder etc dynamically.

公平地说,这个脚本示例在显示正在发生的事情方面可能不是很明确,我不得不即时进行更改以修复新的对象样式,但本质上:它创建连接对象,然后动态扩展它们以包含子文件夹,并以 KB、MB 和 GB 为单位维护每个子文件夹和站点的运行总数,并在动态汇总给定文件夹等的所有目录后显示要报告的值。

While i had to edit it a bit because this is also an earlier version of these as well, I thought it was one of the instances where it might best show the benefits. If I find a better example in one of my other scripts I might update it there as well.

虽然我不得不稍微编辑它,因为这也是这些的早期版本,但我认为这是它可能最能展示好处的实例之一。如果我在我的其他脚本之一中找到更好的示例,我也可能会在那里更新它。

回答by Ben Personick

The following program simulates vectors (arrays) operations in cmd. The subroutines presented in it were initially designed for some special cases like storing the program parameters in an array or looping through filenames in a "for" loop and storing them in an array. In these cases, in an enabled delayed expansionblock, the "!" characters - if present in values of the parameters or in the "for" loop variable's value - would get interpreted. That's why, in these cases, the subroutines must be used inside a disabled delayed expansionblock:

以下程序模拟cmd. 其中提供的子程序最初是为一些特殊情况设计的,例如将程序参数存储在数组中或在“ for”循环中循环文件名并将它们存储在数组中。在这些情况下,在enabled delayed expansion块中," !" 字符 - 如果出现在参数值或 " for" 循环变量的值中 - 将被解释。这就是为什么,在这些情况下,子程序必须在disabled delayed expansion块内使用:

@echo off

rem The subroutines presented bellow implement vectors (arrays) operations in CMD

rem Definition of a vector <v>:
rem      v_0 - variable that stores the number of elements of the vector;
rem      v_1..v_n, where n=v_0 - variables that store the values of the vector elements.


rem :::MAIN START:::

setlocal disabledelayedexpansion

    rem Getting all the parameters passed to the program in the vector 'params':
    rem Delayed expansion is left disabled in order not to interpret "!" in the program parameters' values (%1, %2, ... );
    rem If a program parameter is not quoted, special characters in it (like "^", "&", "|") get interpreted at program launch.
:loop1
    set "param=%~1"
    if defined param (
        call :VectorAddElementNext params param
        shift
        goto :loop1
    )
    rem Printing the vector 'params':
    call :VectorPrint params

    pause&echo.

    rem After the vector variables are set, delayed expansion can be enabled and "!" are not interpreted in the vector variables's values:
    echo Printing the elements of the vector 'params':
    setlocal enabledelayedexpansion
        if defined params_0 (
            for /l %%i in (1,1,!params_0!) do (
                echo params_%%i="!params_%%i!"
            )
        )
    endlocal

    pause&echo.

    rem Setting the vector 'filenames' with the list of filenames in the current directory:
    rem Delayed expansion is left disabled in order not to interpret "!" in the %%i variable's value;
    for %%i in (*) do (
        set "current_filename=%%~i"
        call :VectorAddElementNext filenames current_filename
    )
    rem Printing the vector 'filenames':
    call :VectorPrint filenames

    pause&echo.

    rem After the vector variables are set, delayed expansion can be enabled and "!" are not interpreted in the vector variables's values:
    echo Printing the elements of the vector 'filenames':
    setlocal enabledelayedexpansion
        if defined filenames_0 (
            for /l %%i in (1,1,!filenames_0!) do (
                echo filenames_%%i="!filenames_%%i!"
            )
        )
    endlocal

    pause&echo.

endlocal
pause

rem :::MAIN END:::
goto :eof


:VectorAddElementNext
rem Vector Add Element Next
rem adds the string contained in variable %2 in the next element position (vector length + 1) in vector %1
(
    setlocal enabledelayedexpansion
        set "elem_value=!%2!"
        set /a vector_length=%1_0
        if not defined %1_0 set /a vector_length=0
        set /a vector_length+=1
        set elem_name=%1_!vector_length!
)
(
    endlocal
    set "%elem_name%=%elem_value%"
    set %1_0=%vector_length%
    goto :eof
)

:VectorAddElementDVNext
rem Vector Add Element Direct Value Next
rem adds the string %2 in the next element position (vector length + 1) in vector %1
(
    setlocal enabledelayedexpansion
        set "elem_value=%~2"
        set /a vector_length=%1_0
        if not defined %1_0 set /a vector_length=0
        set /a vector_length+=1
        set elem_name=%1_!vector_length!
)
(
    endlocal
    set "%elem_name%=%elem_value%"
    set %1_0=%vector_length%
    goto :eof
)

:VectorAddElement
rem Vector Add Element
rem adds the string contained in the variable %3 in the position contained in %2 (variable or direct value) in the vector %1
(
    setlocal enabledelayedexpansion
        set "elem_value=!%3!"
        set /a elem_position=%2
        set /a vector_length=%1_0
        if not defined %1_0 set /a vector_length=0
        if !elem_position! geq !vector_length! (
            set /a vector_length=elem_position
        )
        set elem_name=%1_!elem_position!
)
(
    endlocal
    set "%elem_name%=%elem_value%"
    if not "%elem_position%"=="0" set %1_0=%vector_length%
    goto :eof
)

:VectorAddElementDV
rem Vector Add Element Direct Value
rem adds the string %3 in the position contained in %2 (variable or direct value) in the vector %1
(
    setlocal enabledelayedexpansion
        set "elem_value=%~3"
        set /a elem_position=%2
        set /a vector_length=%1_0
        if not defined %1_0 set /a vector_length=0
        if !elem_position! geq !vector_length! (
            set /a vector_length=elem_position
        )
        set elem_name=%1_!elem_position!
)
(
    endlocal
    set "%elem_name%=%elem_value%"
    if not "%elem_position%"=="0" set %1_0=%vector_length%
    goto :eof
)

:VectorPrint
rem Vector Print
rem Prints all the elements names and values of the vector %1 on sepparate lines
(
    setlocal enabledelayedexpansion
        set /a vector_length=%1_0
        if !vector_length! == 0 (
            echo Vector "%1" is empty!
        ) else (
            echo Vector "%1":
            for /l %%i in (1,1,!vector_length!) do (
                echo [%%i]: "!%1_%%i!"
            )
        )
)
(
    endlocal
    goto :eof
)

:VectorDestroy
rem Vector Destroy
rem Empties all the elements values of the vector %1
(
    setlocal enabledelayedexpansion
        set /a vector_length=%1_0
)
(
    endlocal
    if not %vector_length% == 0 (
        for /l %%i in (1,1,%vector_length%) do (
            set "%1_%%i="
        )
        set "%1_0="
    )
    goto :eof
)

It is also possible to store the program parameters in an "array" or loop through the filenames in a directory using a "for" loop and store them in an "array" (without interpreting "!" in their values) without using the presented subroutines in the program above:

另外,也可以使用一个“程序参数存储在一个“阵列”或循环通过在一个目录中的文件名for”循环,并将它们存储在一个“阵列”(不解释“ !”在它们的值),而不使用在所呈现的子程序上面的程序:

@echo off

setlocal disabledelayedexpansion

    rem Getting all the parameters passed to the program in the array 'params':
    rem Delayed expansion is left disabled in order not to interpret "!" in the program parameters' values (%1, %2, ... );
    rem If a program parameter is not quoted, special characters in it (like "^", "&", "|") get interpreted at program launch.
    set /a count=1
:loop1
    set "param=%~1"
    if defined param (
        set "params_%count%=%param%"
        set /a count+=1
        shift
        goto :loop1
    )
    set /a params_0=count-1

    echo.

    rem After the array variables are set, delayed expansion can be enabled and "!" are not interpreted in the array variables's values:
    rem Printing the array 'params':
    echo Printing the elements of the array 'params':
    setlocal enabledelayedexpansion
        if defined params_0 (
            for /l %%i in (1,1,!params_0!) do (
                echo params_%%i="!params_%%i!"
            )
        )
    endlocal

    pause&echo.

    rem Setting the array 'filenames' with the list of filenames in the current directory:
    rem Delayed expansion is left disabled in order not to interpret "!" in the %%i variable's value;
    set /a count=0
    for %%i in (*) do (
        set "current_filename=%%~i"
        set /a count+=1
        call set "filenames_%%count%%=%%current_filename%%"
    )
    set /a filenames_0=count

    rem After the array variables are set, delayed expansion can be enabled and "!" are not interpreted in the array variables's values:
    rem Printing the array 'filenames':
    echo Printing the elements of the array 'filenames':
    setlocal enabledelayedexpansion
        if defined filenames_0 (
            for /l %%i in (1,1,!filenames_0!) do (
                echo filenames_%%i="!filenames_%%i!"
            )
        )
    endlocal

endlocal
pause

goto :eof

回答by T3RR0R

Searching an Array to find if a value is contained in any index:

搜索数组以查找某个值是否包含在任何索引中:

@echo off
Setlocal EnableDelayedExpansion
(For /L %%A in (1,2,3) do For /L %%B in (1,1,10) do Set /A ArrayName[%%A][%%B]=%%A * %%B)&REM Example Array
Set "ArrayName[2][8]=test"& Set "ArrayName[1][6]=string check"
For %%A in (3 5 8 12 test string "String Check") do Call :Search.Array "%%~A" ArrayName
Pause & Endlocal & Exit /B
:Search.Array <Search Value> <Array Class Name>
For %%S in ("%~1") do (
    For /F "USEBACKQ Tokens=1,2 Delims==" %%A in (`"Set %~2["`) Do (
        For /F "USEBACKQ Tokens=* Delims=" %%F in (`Echo("%%~B"^|%__AppDir__%Findstr.exe /I "%%~S"`) do (
            If /I "%%~S" == "%%~F" (Echo %%~S found in %%~A = %%~B)
        )&REM remove /I switch from FindStr if case sensitivity required
    )
)
Exit /B

Output:

输出:

3 found in ArrayName[1][3] = 3
3 found in ArrayName[3][1] = 3
5 found in ArrayName[1][5] = 5
8 found in ArrayName[1][8] = 8
12 found in ArrayName[3][4] = 12
test found in ArrayName[2][8] = test
String Check found in ArrayName[1][6] = string check
Press any key to continue . . . 

Building Lists, Extracting Elements and Building new Lists / Strings:

构建列表、提取元素和构建新列表/字符串:

@Echo off 

:main
    Set "List.Var=Call :Extract "
    Setlocal EnableDelayedExpansion
    For %%A in (an example of building a list in the form of a string and extracting specific elements from the list) do IF Not "!List!" == "" (
        Set "List=!List!{%%A}"
        Set /A List#+=1
    ) Else (
        Set "List={%%A}"
        Set /A List#+=1
    )
    Echo.%List%
    Echo.
    Echo Extracting specific String Elements:
    For %%A in (2 3 14 15 16 17 18 19 13 4 5 12) do (
        !List.Var! List %%A ReturnVar
    )
    Echo(!ReturnVar!
    Pause
    Endlocal
Exit /B

:Extract <List Variable Name> <List Item Number> <Return Variable Name>
    Setlocal EnableDelayedExpansion
    REM CALL Set of the For /F string is required to successfully expand the command.
    REM the For Command cannot be expanded with ! Expansion
    CALL Set Extract.Item=For /F "tokens=%%~2 Delims={}" %%%%O in ("%%%~1%%") DO Set Result=%%%%O
    %Extract.Item%
    REM Endlocal & Set "%~3=%Result%"
    Endlocal & IF Not "!%~3!" == "" (Set "%~3=!%~3! %Result%") Else (Set 
    "%~3=%Result%")
Exit /B

Output:

输出:

Example Output

示例输出

This approach shares Similarities with Arrays in the sense that elements are accessed via an Index Number, however differs significantly in terms of memory space used to store the values - as all values for the List are contained within The one variable.

这种方法与数组的相似之处在于元素是通过索引号访问的,但是在用于存储值的内存空间方面有很大不同 - 因为 List 的所有值都包含在一个变量中。

The concept of this one is fairly simple - It's string manipulation by Index number as opposed to substring modification.

这个概念相当简单——它是通过索引号进行字符串操作,而不是子字符串修改。

You build your values into a string, Delimiting them as you go. I've opted for {Braces }as the Delims for values, as they do not conflict with other common scripting forms within batch, maintaining readability.

你将你的值构建成一个字符串,在你进行时对它们进行分隔。我选择了{大括号}作为值的 Delim,因为它们不会与批处理中的其他常见脚本形式发生冲突,从而保持可读性。

As noted, Call Setis required when defining the token the For /Floop is to access within the Extraction Subroutine.

如上所述,Call Set在定义For /F循环要在提取子例程中访问的令牌时需要。



Population of an array construct using a Macro

使用宏填充数组构造

I constructed the below macro to mimic piping by assigning input arguments to a variable processed by an internal for loop at the moment it's expanded.

我构造了下面的宏来模拟管道,方法是将输入参数分配给由内部 for 循环在扩展时处理的变量。

Essentially, the preface to expanding the population macro is:

从本质上讲,扩大人口宏观的序言是:

%Params|%

%Params|%

Which expands to:

其中扩展为:

Set Args-In=

Set Args-In=

And the Input Args to be processed by the macro is appended in doubleqouted form:

宏要处理的 Input Args 以双引号形式附加:

"arg 1" "arg 2"

"arg 1" "arg 2"

All together:

全部一起:

%Params|% "Object OneA" "Object TwoA" "Object ThreeA" %Define.Array% ClassName IndexName

%Params|% "Object OneA" "Object TwoA" "Object ThreeA" %Define.Array% ClassName IndexName

Joined to the outside of the Define.Array macro For loop using:

使用以下方法连接到 Define.Array 宏 For 循环的外部:

^&For

^&For

The Macro increments counts for the class name and index name using those names as the variable for the count. Each time the macro is expanded for a given class name or index name, the value of that count will be incremented. Class names will be appended with the next class index number with each expansion. The first use of Classwill become Class[1]the second Class[2]and so on. Index names should be different for each expansion of the macro for the sake of accurate referencing. Each element contained within a class name is assigned to it's own indexed value for the current class index. Eg Class[1][1]Class[1][2]

宏使用这些名称作为计数变量来增加类名和索引名的计数。每次为给定的类名或索引名扩展宏时,该计数的值都会增加。每次扩展时,类名将附加下一个类索引号。第一次使用Class将变成Class[1]第二次Class[2],依此类推。为了准确引用,宏的每次扩展的索引名称应该不同。类名中包含的每个元素都分配给它自己的当前类索引的索引值。例如Class[1][1]Class[1][2]

Update

更新

Solution to the recursion level issue: Removal of the Setlocal EnableDelayedExpansionswitch from the macro and turning Delayed expansion on prior to expanding the macro resolves this issue.

递归级别问题的解决方案:Setlocal EnableDelayedExpansion从宏中移除开关并在扩展宏之前打开延迟扩展可解决此问题。

@Echo off 

    Setlocal DisableDelayedExpansion

    (Set LF=^


    %= NewLine =%)

    Set ^"\n=^^^%LF%%LF%^%LF%%LF%^^"
::: \

::: / Macro for recieving exceptionally large arguments
Set "Inc.Class=Set /A %%G+=1"
Set "Inc.Index=Set /A %%H+=1"

Endlocal & Set Define.Array=^&for /L %%n in (1 1 2) do if %%n==2 (%\n%
        For /F "tokens=1,2 delims=, " %%G in ("!argv!") do (%\n%
            %Inc.Class% %\n%
%= iterate over the tempVar for each argument to be processed =%
            For %%A in (!Args-In!) do (%\n%
%= Increment the count for the current array name =%
                %Inc.Index% %\n%
%= Assign the current arg to the current Array Index =%
                Set "%%G[!%%G!][!%%H!]=%%~A" %\n%
            ) %\n%
%= Store the Arg-In to reference list of all args for future reuse =%
            Set Ref_%%G[!%%G!]=!Args-In! %\n%
        ) %\n%
    ) ELSE set argv=, 
::: \

::: / mini-Macro to Pseudo pipe complex strings into Macros.
    Set "Params|=Set Args-In="
::: \

:main
::: / 'Switch on' Define.Array Macro in advance. This is in order to resolve the Setlocal recursion breach that occurs if Expansion is enabled within the macro.
    Setlocal enableDelayedExpansion
::: \

::: / Example usage of paired macro's
::: - Demonstrate definition fo linked element names to matrices
%Params|% "Object_A" "Object_B" "Object_C" "Object_D" %Define.Array% Class I1 
%Params|% "Object_E" "Object_F" "Object_G" "Object_H" %Define.Array% Element I2
%Params|% "Object_I" "Object_J" "Object_K" "Object_L" %Define.Array% Array I3
%Params|% "Object_M" "Object_N" "Object_O" "Object_P" %Define.Array% Variable I4
%Params|% "Object_Q" "Object_R" "Object_S" "Object_T" %Define.Array% Element I5
%Params|% "Object_U" "Object_V" "Object_W" "Object_X" %Define.Array% Array I6
%Params|% "Object_Y" "Object_Z" "Object_0" "Object_1" %Define.Array% Variable I7
%Params|% "Object_2" "Object_3" "Object_4" "Object_5" %Define.Array% Class I8
%Params|% "Object_6" "Object_7" "Object_8" "Object_9" %Define.Array% Element I9
::: - Demonstrate building values obtained during a for loop into a string to be fed into Define.Array
%Params|% & (For %%A in ("example of" "string building" "Using a For Loop" "^(If defined, the Reset of the Args-In Variable is required^)") do (%Params|%!Args-In! "%%~A")) %Define.Array% BuildString I10


::: Example of ways to Iterate over the Array values.
For %%A in (Class Element Array Variable BuildString) do (
    For /L %%C in (1,1,!%%A!) do (
%= subroutine builds a string containing all index values for later reuse by referring to the element and index names =%
        Set Ref_%%A[%%C]
%= I# allows a unique reference number for each array to iterate over that array with =%
        For /L %%I in (1,1,!I%%C!) do (
            If not "!%%A[%%C][%%I]!"=="" Set %%A[%%C][%%I]
        )
    Echo.
    )
    Echo.
)

For %%C in (Class Element Array Variable BuildString) do For /L %%E in (1,1,!%%C!) do For %%I in (!Ref_%%C[%%E]!) Do Echo %%~I

Pause >nul
Exit /B
::: \

Output:

输出:

Ref_Class[1]= "Object_A" "Object_B" "Object_C" "Object_D" 
Class[1][1]=Object_A
Class[1][2]=Object_B
Class[1][3]=Object_C
Class[1][4]=Object_D

Ref_Class[2]= "Object_2" "Object_3" "Object_4" "Object_5" 
Class[2][1]=Object_2
Class[2][2]=Object_3
Class[2][3]=Object_4
Class[2][4]=Object_5


Ref_Element[1]= "Object_E" "Object_F" "Object_G" "Object_H" 
Element[1][1]=Object_E
Element[1][2]=Object_F
Element[1][3]=Object_G
Element[1][4]=Object_H

Ref_Element[2]= "Object_Q" "Object_R" "Object_S" "Object_T" 
Element[2][1]=Object_Q
Element[2][2]=Object_R
Element[2][3]=Object_S
Element[2][4]=Object_T

Ref_Element[3]= "Object_6" "Object_7" "Object_8" "Object_9" 
Element[3][1]=Object_6
Element[3][2]=Object_7
Element[3][3]=Object_8
Element[3][4]=Object_9


Ref_Array[1]= "Object_I" "Object_J" "Object_K" "Object_L" 
Array[1][1]=Object_I
Array[1][2]=Object_J
Array[1][3]=Object_K
Array[1][4]=Object_L

Ref_Array[2]= "Object_U" "Object_V" "Object_W" "Object_X" 
Array[2][1]=Object_U
Array[2][2]=Object_V
Array[2][3]=Object_W
Array[2][4]=Object_X


Ref_Variable[1]= "Object_M" "Object_N" "Object_O" "Object_P" 
Variable[1][1]=Object_M
Variable[1][2]=Object_N
Variable[1][3]=Object_O
Variable[1][4]=Object_P

Ref_Variable[2]= "Object_Y" "Object_Z" "Object_0" "Object_1" 
Variable[2][1]=Object_Y
Variable[2][2]=Object_Z
Variable[2][3]=Object_0
Variable[2][4]=Object_1


Ref_BuildString[1]=  "example of" "string building" "Using a For Loop" "(If defined, the Reset of the Args-In Variable is required)"
BuildString[1][1]=example of
BuildString[1][2]=string building
BuildString[1][3]=Using a For Loop
BuildString[1][4]=(If defined, the Reset of the Args-In Variable is required)


Object_A
Object_B
Object_C
Object_D
Object_2
Object_3
Object_4
Object_5
Object_E
Object_F
Object_G
Object_H
Object_Q
Object_R
Object_S
Object_T
Object_6
Object_7
Object_8
Object_9
Object_I
Object_J
Object_K
Object_L
Object_U
Object_V
Object_W
Object_X
Object_M
Object_N
Object_O
Object_P
Object_Y
Object_Z
Object_0
Object_1
example of
string building
Using a For Loop
(If defined, the Reset of the Args-In Variable is required)



A simple approach to populating multiple Arrays using a function.

使用函数填充多个数组的简单方法。

The example Function provided assigns values to Array Indices using Call with a parameter for the element name - An unexpanded Variable that has previously been set with the values of all indices to be populated. The second parameter is used to define the index number for the Array being populated, and is associated with the Element name for future access of the Array.

提供的示例函数使用带有元素名称参数的 Call 将值分配给数组索引 - 一个未扩展的变量,之前已使用要填充的所有索引的值进行设置。第二个参数用于定义正在填充的数组的索引号,并与元素名称相关联,以便将来访问数组。

The example also demonstrates how to create and access nested arrays, as well as how to build strings for use in array population during For Loops.

该示例还演示了如何创建和访问嵌套数组,以及如何在 For 循环期间构建用于数组填充的字符串。

    @Echo Off & Setlocal EnableDelayedExpansion & Title Batch Nested Array & Goto :Main

:Populate <Element Var Name> <Element Index VarName>

    Set %2=0
    For %%A in (!%1!) Do (
        Set /A "%2+=1"
        Set "%1[!%2!]=%%A"
    )
    Exit /B

:Main

REM Define values of variables within array indices (As Examples)
    For %%A in (one,two,three,four,five) Do (
        Set /A %%A=!random! * 1500 / 32768 + 1
REM Build Array set list for Populate Subroutine. This method of building the set string can be applied to any For Loop.
        If Defined group1 (Set "group1=!group1!,%%A") Else (Set "group1=%%A")
    )

    For %%A in (_One,_Two,_Three,_Four,_Five) Do (
        Set /A %%A=!random! * 1500 / 32768 + 1
        If Defined group2 (Set "group2=!group2!,%%A") Else (Set "group2=%%A")
    )

Call :Populate group1 _A
Call :Populate group2 _B

    For %%A in (group1,group2) Do (If Defined Nest (Set "Nest=!Nest!,%%A") Else (Set "Nest=%%A"))
    Call :Populate Nest _C

Rem Display Array Element Names and Values

    For /L %%A in (1,1,!_A!) Do (For %%B In (!group1[%%A]!) Do (ECHO(group1[%%A] : %%B = !%%B!))
    For /L %%A in (1,1,!_B!) Do (For %%B In (!group2[%%A]!) Do (ECHO(group2[%%A] : %%B = !%%B!))

REM Accessing Variables from the Nested Array (Every Tier):

    FOR /L %%A in (1,1,!_C!) Do (
        For %%B In (!Nest[%%A]!) Do (
            ECHO(Nest[%%A] : !Nest[%%A]! = !%%B!
            Set count=0
            For %%C In (!%%B!) Do (
                Set /A count+=1
                ECHO(Nest[%%A] : %%B[!count!] = %%C = !%%C!)
            )
        )
    )

    Pause
    Exit

Located here is a script that demonstrates the use of nested for loops to sort and compare elements in Arrays

位于此处的脚本演示了使用嵌套 for 循环对数组中的元素进行排序和比较

In all the playing around I've done with bath arrays in the past few month's, how did I miss using Set ArrayName[as a means of accessing or storing the Array. Thanks@Rojo

在过去的几个月里,我一直在玩沐浴阵列,我怎么会错过使用它Set ArrayName[作为访问或存储阵列的手段。 谢谢@Rojo

So much more efficient. It allows array constructs to be Rapidly stored using:

效率更高。它允许使用以下方法快速存储数组结构:

For /F "USEBACKQ Delims=" %%A in (`"Set Array["`) Do >>%Temp%\ArrayName.bat Echo(Set %%A

And rapidly retrieve them by Doing:

并通过 Doing 快速检索它们:

For /f "delims=" %%A in (%Temp%\ArrayName.txt) do %%A

Even more fantastically,It makes an operation to search array values much, much easier.

更神奇的是,它使搜索数组值的操作变得更加容易。

For %%S in (3 5 8) do For /F "USEBACKQ Delims=" %%A in (`"Set Array["`) Do Echo(%%A| Find "=%%S"


An unusual, impractical, however still somewhat interesting way to create linked data within a batch file is to assign variables within variables within variables, with the aid (Or abuse) of Callto extract the desired values.

在批处理文件中创建链接数据的一种不寻常的、不切实际的但仍然有些有趣的方法是在变量内的变量内分配变量,帮助(或滥用)Call提取所需的值。

Depending on the length of the string contained within a variable, A variable depth of Eleven can be achieved - That is to say, from the same starting point, eleven data points can be stored / extracted. Due to the relationship between the values of how many Expansions are required to access a particular depth of the variable, a function could easily be built to extract a value at that depth.

根据包含在变量中的字符串的长度,可以实现 11 的可变深度——也就是说,从同一个起点,可以存储/提取 11 个数据点。由于访问变量的特定深度所需的扩展值之间的关系,可以轻松构建函数来提取该深度的值。

As mentioned, it is impractical, not to mention terribly inefficient, however I find it interesting enough to merit mentioning.

如前所述,这是不切实际的,更不用说非常低效了,但是我觉得它很有趣,值得一提。

The included script is somewhat basic, merely built to show proof of concept - not a practical application by any means.

包含的脚本有点基础,只是为了展示概念证明 - 无论如何都不是实际应用。

The basis of the principle is that with the use of Call, A variable can be expanded at increasing levels of depth, until the variable is found to be empty. The limitation is in the line length limit, as the number of %expansions that need to be used to expand the variable increases proportionally to the number of Calls used to Parse over the variable for each level of expansion to be achieved.

原理的基础是,通过使用 Call,可以在增加的深度级别上扩展变量,直到发现变量为空。限制在于行长度限制,因为%需要用于扩展变量的扩展数量与用于解析变量的调用数量成比例增加,以实现每个级别的扩展。

The following rules apply to expansion depth using Call:

以下规则适用于使用 Call 的扩展深度:

  • E (% Expansion) begins with a value of 1
  • The value of C (Calls required to parse the Variable) commences at 0, increments by 1 for the 2nd expansion, Then doubles for Each subsequent Expansion
  • E (% Expansion) 从值 1 开始
  • C 的值(解析变量所需的调用)从 0 开始,第二次扩展时增加 1,然后在每个后续扩展中加倍

The value of E increases in proportion to C like so:

E 的值与 C 成比例增加,如下所示:

  • E = ( C * 4 ) -1
  • E = ( C * 4 ) -1

The following Table expresses the values required to parse the line in terms of number of Calls and % expansions to be appended to the string.

下表根据要附加到字符串的调用数和 % 扩展来表示解析该行所需的值。

From the 12th expansion onwards, the number of Calls and %expansions appended to the string exceed the line length limit, resulting in failure to build a string of the length needed to expand at this point.

从第 12 次扩展开始,%附加到字符串的 Calls 和扩展的数量超过了行长度限制,导致此时无法构建扩展所需长度的字符串。

Use of the double pipe after the Call string can test for when this failure occurs, allowing intervention:

在 Call 字符串之后使用双管道可以测试何时发生此故障,从而允许干预:

!Assign! || (Echo String exceeds line length, No Modification. & Exit /B)

!Assign! || (Echo String exceeds line length, No Modification. & Exit /B)

To offset the string length limitation limitation, the variable used as the starting point can be changed in combination with the depth level to target.

为了抵消字符串长度限制,可以结合目标深度级别更改用作起点的变量。

Call Expansion Table

呼叫扩展表

@echo off

Set Zero=One
Set One=Two
Set Two=Three
set Three=Four
set Four=Five
set Five=Six
set Six=Seven
set Seven=Eight
set Eight=Nine
Set Nine=Ten
Set Ten=Eleven
Set Eleven=Twelve
Set Twelve=Thirteen. Last variable. Cannot Be reached from depth 0

Goto :main

REM Subroutine to Build Expansion String based on depth:

:extract <Starting Variable> <Call Depth Range 1 - 11> <Variable to Assign>
    Setlocal EnableDelayedExpansion
    Set "Assign=%~1"
    Set EXPcount=1
    Set "CALLcount=%~2"
    IF Not "!CALLcount!"=="0" Set /A EXPcount=( !CALLcount! * 4 ) - 1
    For /L %%A in (1,1,!EXPcount!) do (Set "Assign=%%!Assign!%%")

    Setlocal DisableDelayedExpansion
    Set "Assign=Set Assign=!Assign!"
    (
    Endlocal
        Set "Assign=%Assign%"
    )

    For /L %%B in (0,1,!CALLcount!) do (Set "Assign=CALL !Assign!")
    !Assign! || (Echo String exceeds line length, No Modification. & Exit /B)
    IF "!Assign!"==" " (Echo Variable Not Defined, No Modification. & Exit /B)
    (
    Endlocal
        Set "%~3=%Assign%"
        Exit /B
    )


REM Expand variable using relationship between Number of Calls and % Expansion Required to Parse variable
:main
Setlocal EnableDelayedExpansion

REM Define variable 'Depth Shortcut' to target specific depths
For %%A in (0 1 2 4 8 16 32 64 128 256 512 1024) Do (
    Set /A #+=1
    Set Depth[!#!]=%%A
)

REM iterate over all depths
For /L %%E in (1,1,!#!) do (
    Call :Extract Zero !Depth[%%E]! New
    Echo(Start: Zero Depth: %%E, Extracted Value: !New!
)
ECHO.
REM extract from selected depth levels
For %%E in (5 8 11) do (
    Call :Extract Zero !Depth[%%E]! New
    Echo(Start: Zero Depth: %%E, Extracted Value: !New!
)
ECHO.
REM extract from selected depth levels, From a different starting Variable
For %%E in (5 6 7 8 9) do (
    Call :Extract five !Depth[%%E]! New
    Echo(Start: Five Depth: %%E, Extracted Value: !New!
)
Endlocal
Echo(Example Complete
Pause >nul

Example Output

示例输出



回答by Antares

On the subject of "Turing Completeness in Batch programming"

关于“批量编程中的图灵完备性”主题

Yes, Batch is Turing complete according to best of my knowledge (and if you ignore the "unlimited" features like unlimited memory and computational time; so one could argue that Batch is only "theoretically Turing equivalent").

是的,据我所知,Batch 是图灵完备的(如果您忽略“无限”功能,如无限内存和计算时间;所以有人可能会争辩说 Batch 只是“理论上图灵等效”)。

There are all basic boolean and arithmetic operators as well as loops (for) and branching (if). Also there is a gotofunctionality which allows modeling loops (while/do while/for) and sub routines. Nesting of blocks is possible. Variablescan be named, stored, deleted/cleared, displayed/written to file. A haltcondition can be reached with exit(or goto eof).
As a sidenote: It is possible to write a batch file from inside a batch program, write it to disk and run it (allows self modification/customization/sub routines/state saving and restoring).

有所有基本的布尔和算术运算符以及循环 ( for) 和分支 ( if)。也有一个goto其允许建模循环(功能while/ do while/ for)和sub routines。块的嵌套是可能的。Variables可以命名、存储、删除/清除、显示/写入文件。甲halt条件可以与达到exit(或GOTO EOF)。
作为旁注:可以从批处理程序内部写入批处理文件,将其写入磁盘并运行它(允许自我修改/自定义/子例程/状态保存和恢复)。

However there is no unlimited memory storage. Only 32-Bit arithmetics can be used in computations. And obviously the computer running the batch file also has hardware and physical limits (only finite time, speed or space).

但是,没有无限的内存存储。在计算中只能使用 32 位算法。显然,运行批处理文件的计算机也有硬件和物理限制(只有有限的时间、速度或空间)。

It should be noted that all "higher level" concepts you mentioned are not part of the "batch programming language". There is no concept of classes, objects, records/structs, arrays, linked lists, stacks, queues, etc integrated. Nor are there any default algorithms like sorting, etc. provided (except maybe if sortor findStr, moreetc. with pipes are taken into consideration). Randomizing is also very basic with the %RANDOM%variable.
If you need those concepts, you need to model them with the given basic language elements I mentioned above on your own (or use some library/third-party-batchfiles for that matter).
Of course it is possible to callnot only batch files but any supplemental program on the computer and return to the batch execution afterwards (communicating via file, standard I/O streams or exit/errorlevel codes). Those programs could have been written in higher level languages that provides those sorts of things in a more convenient way.

应该注意的是,您提到的所有“高级”概念都不是“批处理编程语言”的一部分。没有集成类、对象、记录/结构、数组、链表、堆栈、队列等概念。也没有提供任何缺省算法,如排序等(可能除了如果sortfindStrmore等用管道考虑)。随机化也是非常基础的%RANDOM%变量。
如果您需要这些概念,则需要使用我上面提到的给定基本语言元素自行对它们进行建模(或者为此使用一些库/第三方批处理文件)。
当然也可以call不仅是批处理文件,还包括计算机上的任何补充程序,然后返回批处理执行(通过文件、标准 I/O 流或退出/错误级别代码进行通信)。这些程序本可以用更高级的语言编写,以更方便的方式提供这些东西。

From my point of view Bash(Linux) and Powershell(Windows/Linux) are far more advanced in those fields.

在我看来,Bash(Linux) 和Powershell(Windows/Linux) 在这些领域要先进得多。

回答by tux

@echo off

set array=

setlocal ENABLEEXTENSIONS ENABLEDELAYEDEXPANSION

set nl=^&echo(

set array=auto blue ^!nl!^
  bycicle green ^!nl!^
  buggy   red

echo convert the String in indexed arrays

set /a index=0

for /F "tokens=1,2,3*" %%a in ( 'echo(!array!' ) do (

 echo(vehicle[!index!]=%%a color[!index!]=%%b 
 set vehicle[!index!]=%%a
 set color[!index!]=%%b
 set /a index=!index!+1   

)

echo use the arrays

echo(%vehicle[1]% %color[1]%
echo oder

set index=1
echo(!vehicle[%index%]! !color[%index%]!