批处理脚本中的数组、链表和其他数据结构

80

我在使用cmd.exe时,发现它的帮助文档中没有提供如何定义数组的信息。

我已经找到了如何定义简单变量的方法:

set a=10
echo %a%

我想创建数组、链表等数据结构,但是在cmd.exe中是否可以实现呢?(我的意思是:cmd.exe中是否存在任何数组关键字?)

我想要实现一些算法,例如:

  • 冒泡排序
  • 快速排序
  • 侏儒排序

等等...

因此,我还想知道,Cmd.exe是否有参考或实例、结构体等内容?

因为它的帮助信息并不充分:/?

Cmd.exe能否根据图灵机的定义被定义为完整的?(图灵完备)


2
@MatteoItalia Linux shell 有这个功能,PowerShell(基于 .NET)也有,不确定 Windows 的 CScript.exe 是否有此功能。 - user1131997
12个回答

211

好的。我会尽可能清晰地表达,以免被误解...

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

这意味着从cmd.exe的角度来看,SET NORMAL_NAME=123SET A#$'()*+,-.?@[\]_{}~=123以及SET VECTOR[1]=123都是正常的变量。因此,由你决定以数组元素的形式编写变量名:

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

这样,echo %elem[2]% 将显示 Second one
如果您想使用另一个变量作为索引,您必须知道在百分号符号中括起来的变量替换为其值是从左到右解析的;这意味着:
set i=2
echo %elem[%i%]%

不给出所期望的结果是因为它意味着:显示elem[变量的值,后跟i,后跟]变量的值。

要解决这个问题,您必须使用延迟扩展,即在开头插入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%]!

您也可以将FOR命令的参数用作索引:for /L %%i in (1,1,3) do echo !elem[%%i]!。当索引在FOR或IF内部改变时,必须使用!index!来存储数组元素的值:set elem[!index!]=新值。要获取索引在FOR / IF内部更改时元素的值,请用双百分号括住该元素,并以 call开头执行该命令。例如,要将一系列数组元素向左移动四个位置:

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

实现前面的过程的另一种方法是使用额外的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]!
)

这样,批处理文件就可以像管理数组一样运行。我认为重要的是不讨论批处理是否管理数组,而是你可以以其他编程语言相同的方式在批处理文件中管理数组。
@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%

请注意,索引值不限于数字,但它们可以是包含有效字符的任何字符串; 这一点允许定义其他编程语言中所谓的关联数组。在此答案中详细解释了使用关联数组解决问题的方法。还要注意空格是变量名中的有效字符,因此您必须注意不要在变量名中插入可能被忽略的空格。
我在这篇文章中阐述了在批处理文件中使用数组表示法的原因。
这篇文章中,有一个批处理文件,它读取文本文件并将行的索引存储在向量中,然后根据行内容对向量元素进行Buble排序; 等效结果是对文件内容进行排序。
本篇文章中,介绍了一种基于文件索引的批处理关系型数据库应用程序。
本篇文章中,介绍了一种完整的批处理多重链表应用程序,它可以组装来自子目录的大型数据结构,并以TREE命令的形式显示出来。

1
不要脸地自我推销:这个答案展示了Array.splice()的批量实现(也依赖于您推荐的array[n]命名约定)。 - rojo
我认为除了 =<NUL> (0x00) 之外的任何字符都是有效的变量名,甚至包括换行符。 - ScriptKidd
1
@HackingAddict1337:看来你是对的。然而,某些字符,比如冒号,会阻止访问变量值。例如:set "var:=value"echo %var:%可以工作,但如果在冒号后插入任何字符,则无法访问变量值(因为%var%替换扩展规则)。 - Aacini

8
Windows shell脚本编程并不是为了与数组甚至复杂的数据结构一起工作而设计的。在Windows shell中,大部分情况下,所有东西都是字符串,但是有些事情你可以做来“处理”数组,比如使用循环声明n个变量VAR_1、VAR_2、VAR_3...并过滤前缀VAR_,或者创建一个分隔符字符串,然后使用迭代分隔符字符串的FOR结构。

同样地,你可以使用相同的基本思路来创建类似于结构体的变量集合,比如ITEM_NAME、ITEM_DATA等。我甚至找到了这个链接,讲述了如何在CMD中模拟关联数组。

当涉及到重型编程时,这一切都非常繁琐和不方便。命令行shell并不是为了进行大量编程而设计的。我同意@MatteoItalia的观点——如果你需要严肃的脚本编程,请使用真正的脚本语言。


你所说的“serious”是什么意思?cmd.exe能否根据图灵机的定义被定义为完整的? - user1131997
2
@magesi CMD确实有一个优点——FOR命令。如果你真的想学习CMD,掌握它然后继续前进。 - trutheality
@trutheality 或者说有一种方法可以基于NT4源代码编写自己的cmd.exe,这样就能够添加一些新功能了 :) - user1131997
@trutheality 谢谢! :) 如果我把结果放在这里,我可以叫你看一下吗? :) - user1131997
1
@magesi:更有用但也够疯狂的工作是逆向工程每个奇怪的批处理语法(我认为甚至在微软都没有官方规范),并修复Wine项目中的cmd.exe。 :) - Matteo Italia
@MatteoItalia 我已经在问题正文中更新了cmd.exe的源代码(cmd.c && cmd.h),现在正在尝试做一些新的东西,就在此时 :) - user1131997

7

我前一段时间用伪数组在批处理中实现了一个冒泡排序算法。 不确定为什么要使用它(虽然我承认在另一个批处理文件中使用过),因为随着列表大小的增加,它会变得非常慢。这更多是为了给自己设定一个小挑战。 有人可能会觉得这很有用。

:: 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

1
不好意思,我不喜欢你的“伪数组”参考。数组主要是一个概念:一组通过索引选择的具有相同名称的元素。您的程序可能会管理数组,也可能不会;没有所谓的“伪数组”东西。请参阅我的先前链接获取更多详细信息... - Aacini
3
@Aacini:“pseudo-arrays”确实存在。当你在一种语言中模拟数组,而该语言的语法或语义中没有提供数组构造时,这些模拟的数组可以被称为“pseudo-arrays”。 - Lightness Races in Orbit
@PreferenceBean:请问您知道 set /A 命令吗?在这个例子中:set /A resul=9+6,您会如何称呼存储在 resul 变量中的 15 _字符串_?“伪数”?“模拟整数”?请记住,批处理文件不提供数字变量! - Aacini
@Aacini:只是让你知道,你一直使用的“对不起”,听起来很粗鲁。我相信这是无意的。但你没有必要为 MS Batch 这么辩护。 - Lightness Races in Orbit
1
@Aacini:我对set /A不是很了解,也不会对批处理做出任何声明。我只是想说,在世界上确实存在“伪数组”的概念。Dave所描述的就像一组PHP变量$var1 = 0; $var2 = 1; $var3 = 2; $i = 2; echo ${var$i};。这是一个数组吗?不是。它是模拟数组的尝试吗?是的。这是一个伪数组。这就是“伪”所意味着的。 - Lightness Races in Orbit

6

关于这个声明*

I have found, how to define simple variables:

set a = 10
echo %a%
这是错误的!变量a将保持为空(假设它最初为空),echo %a%将返回ECHO is on.实际上,名为aSPACE的变量将设置为值SPACE10
因此,为了使代码正常工作,必须去掉等号周围的SPACEs
set a=10
echo %a%

为了使分配对所有字符都安全,请使用带引号的语法(假设您已启用命令扩展,这是Windows命令提示符的默认设置):
set "a=1&0"
echo(%a%

关于您其余的问题,我建议阅读Aacini的一篇全面而精彩的答案


*) 这个语句已经被编辑删除


5
说实话:我从未听说过batch有数组,也许你可以用一些奇怪的技巧来模拟它们,但我不会认为这是一个好主意。
引用/实例/结构是真正语言的东西,cmd脚本只是一堆扩展,它们在原始解释器command.com上生长,你可以进行一些基本的脚本编写,但任何比一堆调用其他命令更复杂的东西都注定会变得丑陋和难以理解。
唯一的“高级”结构是万能的怪异循环for,它与变量替换的奇怪“规则”(%var%%%var!var!,因为愚蠢的解析器而不同)混合使用,使得即使是简单算法的编写也成为了一堆奇怪的技巧(例如,看看这里的快速排序的实现)。
我的建议是,如果你想以明智的方式编写脚本,请使用一个真正的脚本语言,并将batch留给简单、快速的技巧和向后兼容性。

这是一个关于在批处理中创建数组的示例:http://www.msfn.org/board/topic/47265-making-arrays-in-batch/ - user1131997
那不是一个数组,那是一个包含点分隔值的单个字符串变量,使用“for”循环分割。一系列“set”/“for”黑客技巧,就像我说的一样。在这些条件下你会做什么严肃的事情吗? - Matteo Italia
是的,for 命令是你能得到的最接近的了。而且使用它真是太麻烦了。 - Dominic P
@magesi:也许是这样,但是在使用一种连最基本工具都不提供的语言时,有什么好处呢? - Matteo Italia
@MatteoItalia 只是像在 Brainf*ck 中逗弄它一样玩玩而已,当然我不会认真开发它,只是为了疯狂的乐趣。 - user1131997
@MatteoItalia,疯狂的方式有很多种:)例如,在cmd.exe指令中插入__asm(因为在网上有一些带有(w2k)NT4 cmd.exe源代码的源代码,我现在正在查看它们),或尝试在cmd.exe等东西中模拟硬结构和算法。 - user1131997

2
以下程序模拟了向量(数组)在cmd中的操作。其中呈现的子程序最初是为一些特殊情况设计的,例如将程序参数存储在数组中或循环遍历文件名并将它们存储在数组中。在这些情况下,在启用延迟扩展块时,如果参数值或for循环变量的值中存在“!”字符,则会被解释。因此,在这些情况下,子程序必须在禁用延迟扩展块内使用。请注意保留HTML标签。
@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
)

还可以使用“数组”存储程序参数或者使用“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

2

简而言之:

我想到了使用“for”循环和“set”命令来解析变量的方法,从而使我能够创建伪数组,包括有序和链表样式,更重要的是,类似于结构体的伪对象。

一个典型的批处理伪数组及其解析方法:

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

以下我们创建了一些愚蠢的伪数组和手动排序的伪数组,并创建了一个捕获DIR命令输出的有序伪数组。

我们还将愚蠢的伪数组转换为有序数组(在此之后删除原始的愚蠢伪数组变量)。

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

最后,通过预定义的For L循环来动态报告数组中的某些值,该循环的值范围为7到9,并生成一个随机值来打印数组的第4个示例值。

注意:

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

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

@(
 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%=Jack 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

示例结果:

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"

起初,我会像Aacini一样手动地创建变量行,并使用递增计数器进行赋值,或者通过快速变量列表的简单循环进行赋值,这对于小型二维数组来说是没有问题的。然而,当我需要多值内容时,处理长数据数组变得很困难,更不用说在动态匹配和填充多维数组中的内容时,简单的使用方式就无法胜任了。我发现,在需要跨整个板块更新或添加功能的多个信息数组时,它变得很麻烦。因此,一个数组本质上是需要导出为变量的子字符串列表,而添加或更改它们的顺序意味着改变您的代码。例如,假设您需要登录多个FTP服务器,并从某些路径中删除X天前的文件,则最初可能会创建如下所定义的简单子字符串数组:
Site.##=[Array (String)] [Array (String)] @(
       IP=[SubSting],
       Username=[SubString],
       Password[SubString])

或者如此展示,就像这个示例代码一样。
(
  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

现在,13个网站,你肯定会说这还不错,对吧? 你只需要在末尾添加一个并输入信息就可以了。

然后你需要为报告添加网站名称,所以你需要在每个字符串的第5个位置添加另一个术语,这样你就不必更改函数了。

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

然后你意识到需要按照网站名称(或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% "
::...

然后情况变得更糟:

  • 你需要使用不同的用户检查同一站点的目录数量开始增加。

  • 你意识到你需要在每个站点上设置不同的保留时间,以后甚至是每个目录。

  • 你最终拥有了30、40、50个这样的目录,很难通过查看长字符串的结尾并将它们复制来记住哪个是哪个等等。

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

  • 当添加或删除一个目录时,你必须在每个站点上添加或删除它,这使得使用排序更困难,易于忽略站点,因为它们不容易被识别。

真是太痛苦了,而且这还不是当你需要有一个动态对象集时,这都是手动完成的。

那么你能做什么呢?好吧,这就是我所做的:

我最终在我的cmd脚本中实现了一种类似于贫民结构或对象数组(字符串)的排序,以适应需要。

例如,这个结构将是一个“站点对象”,它将具有多个属性,这些属性本身可能是带有子属性的对象。由于CMD实际上不是面向对象的,所以这是一种笨拙的方法,就像数组一样。

由于我开始使用的示例是我尝试这些方法的第一个地方,你可以看到这个中间的混合步骤,我将其定义为:

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])

为了解决需要动态重新排序数据集的问题,我考虑使用链表的形式,但由于我想要在保留站点之间顺序的同时轻松添加每个站点分组的项目,所以我选择了一种简单的方法。
以下是此步骤用法的另一个代码示例:
@(
    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

正如您所看到的,这些结构在需要手动应用并按特定顺序显示数据的分叉层次数据集方面非常有效。
不过,今天我通常将脚本的名称作为结构的基础,因为我发现这更有用,并且根据需要可能会或可能不会使用有序数组。
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])

但是如果你需要收集大量动态生成的数据,并将其分组到预定义的类别中,然后混合报告呢?

同样,在这种情况下,它们也可以很有用,您可以根据需要在代码中动态构建它们并添加更多属性。

与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

公正地说,这个脚本示例可能不太明确展示正在发生的事情,而且我不得不即时更改以修复新的对象样式,但基本上: 它创建连接对象,然后动态扩展它们以包括子文件夹,并在KB、MB和GB中维护每个子文件夹和站点的运行总数,并在汇总给定文件夹的所有目录后,动态选择要报告的值等。 虽然我不得不编辑一下,因为这也是早期版本之一,但我认为这是最能展示其好处的实例之一。如果我在其他脚本中找到更好的示例,我也可能会在那里进行更新。

1

通用数组处理脚本

@ECHO OFF
Set "UseErr=Echo/&Echo/Usage Error - Ensure command extensions and Delayed Expansion are enabled with: &Echo/Setlocal EnableExtensions EnableDelayedExpansion&Echo/ or from the command line:&Echo/CMD /V:On /K&Exit /B 1"
If Not "!Comspec!"=="%Comspec%" (%UseErr%)
(Set "GRPNm="&Set "TAB= "&Set "S_Offset="&Set "mode="&Set "#STDOut="&Set "nGRPNm="&Set "#Order="&Set "#Help="&Set "Inset="&Set "Usage=Echo/###&Exit /B 1") > Nul 2> Nul
(Set "SwParam="&Set "SwFParam="&Set "#ORP#=0"&Set "#FP#=0"&Set "Inset="&Set "#STDOut=0"&Set "GRPNm="&Set "!GRPNm!="&Set "SubEl="&Set "FlNm=%~n0"& Set "Mode="&Set "FindV=") > Nul 2> Nul
If "%~1"=="" (
    Echo/&Echo/Modes:&Echo/ [Def]!TAB!!TAB!!TAB!Define, modify or clear an array.
    Echo/ [Def]!TAB!!TAB!!TAB!Switches:!TAB![/A:Groupname] [/F:Filepath.ext] [/D] [/O:Index#Arg] [/E:Element Sub value] [[element0] ~ [element#]]
    Echo/ [Sort-int]!TAB!!TAB!Sorts array by lowest or highest value using /L or /H switches
    Echo/ [Sort-int]!TAB!!TAB!Switches:!TAB![/A:Groupname] [/N:New Groupname] [/L^|/H] [/D]
    Echo/ [Sort-str]!TAB!!TAB!Sorts an array or text files string values using alphanumerical order of sort: [0-9][a-z]
    Echo/ [Sort-str]!TAB!!TAB!Switches:!TAB![/A:Groupname] [/F:Filepath.ext] [/D]
    Echo/ [Find]    !TAB!!TAB!Searches an array for the string value supplied.
    Echo/ [Find] [searchstring]!TAB!Switches: [/A:Groupname]&Echo/
    %Usage:###=/M:Mode required&Echo/[Def][Sort-int^|str][Find-Value]%
   ) Else Call :GetArgs %*
If Errorlevel 1 Exit /B 1
If "!Mode!"=="" (%Usage:###=/M:Mode  required&Echo/[Def][Sort-int^|str][Find-Value]%)
Call :!Mode! %* 2> Nul || (%Usage:###=Invalid Mode or switch error for /M:!Mode!&Echo/[Def][Sort-int^|str][Find-Value]%)
Exit /B 0
:str
 Set "Usage=Echo/###&Echo/Call !FlNm! ["/F:filepath.ext" ^| "/A:Array Group Name"] & Exit /B 1"
 Set "#!GRPNm!=0"
 If "!#FP#!"=="1" (
  (For /F "UseBackQ Delims=" %%G in (`Type "!FilePath!" ^| Sort`)Do (
   For %%x in ("!GRPNm![!#%GRPNm%!]") Do (
    Setlocal DisableDelayedExpansion
    Endlocal & Set "%%~x=%%~G"
    If "!#STDOut!"=="1" Echo/%%~x=%%~G
   )
   Set /A "#!GRPNm!+=1"
  )) 2> Nul || (%Usage:###:=Echo/Invalid Filepath:"!FilePath!"%)
  Exit /B 0
 )
 If Not "!#FP#!"=="1" (For /F "Tokens=1,2 Delims==" %%G in ('Set !GRPNm![')Do Echo/%%H)>"%TEMP%\__Sort.txt"
 (For /F "UseBackQ Delims=" %%G in (`Type "%TEMP%\__Sort.txt" ^| Sort`)Do (
   For %%x in ("!GRPNm![!#%GRPNm%!]") Do (
    Setlocal DisableDelayedExpansion
    Endlocal & Set "%%~x=%%~G"
    If "!#STDOut!"=="1" Echo/%%~x=%%~G
   )
    Set /A "#!GRPNm!+=1"
  )
 )
 Del /Q "%TEMP%\__Sort.txt"
Exit /B 0
:Find
 Set "Usage=Echo/###&Echo/Call !FlNm! [/M:Find-Searchstring] [/A:Group Name]&Exit /B 1"
 If "!FindV!"=="" (%Usage:###=/M:Find-Value Required%)
 (For /F "Tokens=1,2 Delims==" %%i in ('Set !GRPNm![') Do Echo/"%%j"|"%__AppDir__%findstr.exe"/LIC:"!FindV!" > Nul 2> Nul && (Echo/!FindV! found:&Echo/%%~i=%%~j))
Exit /B 0
:Int
Set "Usage=Echo/###&Echo/Call !FlNm! [/M:Sort-Int] [/A:Group Name] [/N:New Group Name] [Sort-Int] [/H^|/L]&Echo/Call %~n0 [/M:Sort-Int] [/A:Groupname] [Sort-Int] [/H^|/L]&Exit /B 1"
If "!#Help!"=="1" (%Usage:###=/M:Sort-Int Usage:%)
If "!nGRPNm!"=="" Set "nGRPNm=!GRPNm!"
If Not "%#Order%"=="" (Call :Sort%#Order% !nGRPNm! #!nGRPNm! !Inset!) Else (%Usage:###=Sort Order Required /H or /L%)
Exit /B 0
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: Subroutines for Population of Arrays with numeric values in sorted order.
:sortL <Element_VarName> <Element_Index_VarName> <Variable Names containing the values to be Sorted and Populated to the Array>
 Set "%2=0"
 FOR %%P In (%*) DO If Not "%%P"=="%1" If Not "%%P"=="%2" If Not "%%P"=="" (
  Set "%1[!%2!]=!%%P!"
  Set /A "%2+=1"
 )
 For /L %%a In (1,1,!%2!)Do (
  Set /A "S_Offset=%%a - 1"
  For /L %%b IN (0,1,%%a)Do (
   If not %%b==%%a For %%c in (!S_Offset!)Do (
    IF !%1[%%c]! LEQ !%1[%%b]! (
     Set "tmpV=!%1[%%c]!"
     Set "%1[%%c]=!%1[%%b]!"
     Set "%1[%%b]=!tmpV!"
 ))))
 Set /A %2-=1
 If "!#STDOut!"=="1" For /L %%G in (0 1 !%2!)Do Echo/%1[%%G]=!%1[%%G]!
Exit /B 0
:sortH <Element_VarName> <Element_Index_VarName> <Variable Names containing the values to be Sorted and Populated to the Array>
 Set "%2=0"
 FOR %%P In (%*) DO If Not "%%~P"=="%~1" If Not "%%~P"=="%2" If Not "%%P"=="" (
  Set "%1[!%2!]=!%%~P!"
  Set /A "%2+=1"
 )
 For /L %%a In (1,1,!%2!)Do (
  Set /A "S_Offset=%%a - 1"
  For /L %%b IN (0,1,%%a)Do (
   If not %%b==%%a For %%c in (!S_Offset!)Do (
    If Not !%1[%%c]! LSS !%1[%%b]! (
     Set "tmpV=!%1[%%c]!"
     Set "%1[%%c]=!%1[%%b]!"
     Set "%1[%%b]=!tmpV!"
 ))))
 Set /A %2-=1
 If "!#STDOut!"=="1" For /L %%G in (0 1 !%2!)Do Echo/%1[%%G]=!%1[%%G]!
Exit /B 0
:Def
Set "Usage=Echo/###&Echo/Call !FlNm! [/M:Def] [/A:Groupname] ["element0" ~ "element#"] [/F:Filepath.ext] [/E:"Element sub value"]&Echo/ - Assign each line in the given filepath plus element parameters to the Array&Echo/Call %~n0 [/M:Def] [/A:Groupname] REM : Clears the Array for the given Group Name&Echo/Call %~n0 [/M:Def] [/A:Groupname] [element] [element] [/O:Index#Arg] REM : Overides Elements from the index supplied&Exit /B 0"
 If "!#ORP#!"=="1" Echo/!SwParam!|"%__AppDir__%findstr.exe" /RX [0-9]* > Nul 2> Nul
 If not "!SwParam!"=="" If Errorlevel 1 (%Usage:###=O:!SwParam! #Arg invalid. Only Integers accepted.%)
 If "!GRPNm!"=="" (%Usage:###=/A:Groupname Required%)
 If "!#ORP#!"=="1" Set "#!GRPNm!=0"
 If "!#%GRPNm%!"=="" Set "#!GRPNm!=0"
 If "%#FP#%"=="1" (
  If exist "!FilePath!" (
   For /F "Delims=" %%G in (!FilePath!)Do If Not "%%~G"=="" (
    For %%x in ("!GRPNm![!#%GRPNm%!]")Do (
     Setlocal DisableDelayedExpansion
     If "%#STDOut%"=="1" Echo/%%~x=%%~G
     Endlocal & Set "%%~x=%%G"
    )
    Set /A "#!GRPNm!+=1" > Nul
   )
  ) Else (%Usage:###=/F:!FilePath! Invalid path%)
 )
 If not "!Inset!"=="" (
  For %%G in (!Inset!)Do (
   For %%x in ("%GRPNm%[!#%GRPNm%!]")Do (
    Setlocal DisableDelayedExpansion
    If "%#STDOut%"=="1" Echo/%%~x=%%~G
    Endlocal & Set "%%~x=%%~G"
   )
   If Not "!SubEL!"=="" Set "%%~G=!SubEl!"
   Set /A "#!GRPNm!+=1" > Nul
  )
 ) Else (
  If Not "%#FP#%"=="1" (
   For /F "Tokens=1,2 Delims==" %%I in ('Set %GRPNm%')Do Set "%%~I=" > Nul 2> Nul
   Set "#!GRPNm!=" > Nul 2> Nul
  )
 )
Exit /B 0
:GetArgs
 If Not "!#Help!"=="1" If "%~1" == "" (
  If /I "!Mode!"=="int" If "!GRPNm!"=="" (Echo/Call %~n0 [/M:Sort-int] [/A:GroupName] [/H^|/L] [/D]&%Usage:###=/A:Groupname Required%)Else If /I "!Mode!"=="int" (For /F "Tokens=1,2 Delims==" %%G in ('Set !GRPNm![')Do Set "Inset=!Inset! %%G") > Nul 2> Nul || (%Usage:###=Usage Error - /A:!GRPNm! is not defined%)
  If /I "!Mode!"=="str" If "!GRPNm!"=="" (Echo/Call %~n0 [/M:Sort-str] [/A:GroupName] [/N:New Groupname] [/F:Filepath.ext] [/D]&%Usage:###=/A:Groupname Required%)Else If /I "!Mode!"=="str" (For /F "Tokens=1,2 Delims==" %%G in ('Set !GRPNm![')Do Set "Inset=!Inset! %%G") > Nul 2> Nul || (%Usage:###=Usage Error - /A:!GRPNm! is not defined%)
  Exit /B 0
 ) Else If "%~1" == "" Exit /B 0
 Set "Param=%~1"
 Echo/"!Param!"|"%__AppDir__%findstr.exe" /LIC:"Find-" > Nul 2> Nul && ((Set "FindV=!Param:/M:Find-=!"&Set "Mode=Find")&Shift&Goto :GetArgs)
 Echo/"!Param!"|"%__AppDir__%findstr.exe" /LIC:"/M:" > Nul 2> Nul && (
  Set "MODE=!Param:*/M:=!"& Echo/"!Mode!"|"%__AppDir__%findstr.exe" /LIC:"Sort-" > Nul 2> Nul && (Set "Mode=!Mode:*Sort-=!")
  If "!Param:*/M:=!"=="" (
   Echo/&Echo/Modes:&Echo/ [Def]!TAB!!TAB!Define, modify or clear an array.
   Echo/ [Sort-int]!TAB!Sorts array by lowest or highest value using /L or /H switches
   Echo/ [Sort-str]!TAB!Sorts an array or text files string values using alphanumerical order of sort: [0-9][a-z]
   Echo/ [Find:Value]!TAB!Searches an array for the string value supplied.&Echo/
   %Usage:###=/M:Mode required&Echo/[Def][Sort-int^|str][Find-Value]%
  )
  Shift&Goto :GetArgs
 )
 Echo/"%~1"|"%__AppDir__%findstr.exe" /LIC:"/H"  > Nul 2> Nul && (Set "#Order=H"&Shift&Goto :GetArgs)
 Echo/"%~1"|"%__AppDir__%findstr.exe" /LIC:"/L"  > Nul 2> Nul && (Set "#Order=L"&Shift&Goto :GetArgs)
 Echo/"%~1"|"%__AppDir__%findstr.exe" /LIC:"/D"  > Nul 2> Nul && (Set "#STDOut=1"&Shift&Goto :GetArgs)
 Echo/"%~1"|"%__AppDir__%findstr.exe" /LIC:"/F:" > Nul 2> Nul && ((If Not "!Param:/F:=!"=="" (Set "#FP#=1"&Set "FilePath=!Param:/F:=!")Else %Usage:###=/F:Filepath.ext not Supplied%)&Shift&Goto :GetArgs)
 Echo/"%~1"|"%__AppDir__%findstr.exe" /LIC:"/N:" > Nul 2> Nul && (Set "nGRPNm=!Param:*/N:=!"&(If "!Param:*/N:=!"=="" %Usage:###=/N:New Group Name required%)&Shift&Goto :GetArgs)
 Echo/"%~1"|"%__AppDir__%findstr.exe" /LIC:"/A:" > Nul 2> Nul && (Set "GRPNm=!Param:*/A:=!"&(If "!Param:*/A:=!"=="" %Usage:###=/A:Group Name required%)&Shift&Goto :GetArgs)
 Echo/"%~1"|"%__AppDir__%findstr.exe" /LIC:"/O:" > Nul 2> Nul && (Set "SwParam=!Param:*/O:=!"&(If Not "!Param:/O:=!"=="" (Set "#ORP#=1")Else %Usage:###=/O:#Arg not Supplied%)&Shift&Goto :GetArgs)
 Echo/"%~1"|"%__AppDir__%findstr.exe" /LIC:"/E:" > Nul 2> Nul && (Set "SubEl=!Param:*/E:=!"&(If "!Param:/S:=!"=="" %Usage:###=/E:Sub Element not Supplied%)&Shift&Goto :GetArgs)
 Set Inset=!Inset! %1
 Shift&Goto :GetArgs

Modes: - [Def] 定义、修改或清除一个数组。 - [Def] 开关:[/A:组名] [/F:文件路径.扩展名] [/D] [/O:索引#参数] [/E:元素子值] "元素0" ~ "元素#" - [Sort-int] 使用 /L/H 开关,按最低或最高值对数组进行排序。 - [Sort-int] 开关:
[/A:组名] [/N:新组名] [/L|/H] [/D] - [Sort-str] 按字母数字顺序对数组或文本文件字符串值进行排序:[0-9][a-z] - [Sort-str] 开关:
[/A:组名] [/F:文件路径.扩展名] [/D] - [Find-searchstring] 在数组中搜索提供的字符串值。 - [Find-searchstring] 开关:[/A:组名]

1
以下提供了一个处理类似数组的列表并带有多个索引项的示例。
该方法的基础是利用For元变量`~n`修饰符从字符串中剥离实例ID,以便可以直接使用剩余部分。这允许使用列表中的多个元素来访问相同的基础值,而无需定义许多变量。此外,由于存在标识实例编号,因此仍然可以轻松地从列表中删除项目。
变量结构要求任何附加实例都必须以`\`作为分隔符添加前缀,例如:
    \1\itemName

在这里,“itemName”字符串对应于包含所需数据的基本变量,它存在于包含正在通过for循环迭代的项目列表的变量中:

    @Echo off
    Setlocal EnableDelayedExpansion
    Set "itemName=some value"
    Set List="itemName" "someOtherItem" "\1\itemName"
    For %%I in (!List!)Do (
        If not "!%%~nI!" == "" (
            Echo(%%~I=!%%~nI!
        )Else Echo(%%~I not defined
    )
    Pause

1

关于“批处理编程中的图灵完备性”

据我所知,如果忽略“无限”的特性,如无限内存和计算时间,那么批处理是图灵完备的(因此有人可能会认为批处理只是“理论上等同于图灵完备”)。

批处理具备所有基本的布尔和算术运算符,以及循环(for)和分支(if)语句。还有一个goto功能,允许建模循环(while/do while/for)和子程序。块的嵌套也是可行的。变量可以命名、存储、删除/清除、显示/写入文件。通过exit(或goto eof),可以达到halt条件。
顺便说一下:可以从批处理程序内部编写批处理文件,将其写入磁盘并运行它(允许自我修改/自定义/子程序/状态保存和恢复)。

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

需要注意的是,你提到的所有“高级”概念都不属于“批处理语言”。没有类、对象、记录/结构体、数组、链表、栈、队列等集成的概念。也没有提供任何默认算法,如排序等(除非考虑使用管道的sortfindStrmore等)。随机化也非常基础,只有%RANDOM%变量。
如果需要这些概念,需要自己使用我上面提到的基本语言元素进行建模(或使用某些库/第三方批处理文件)。
当然,可以call不仅是批处理文件,还可以调用计算机上的任何补充程序,并在批处理执行后返回(通过文件、标准I/O流或退出/错误级别代码进行通信)。这些程序可以使用更方便的高级语言编写。
在我看来,Bash(Linux)和Powershell(Windows/Linux)在这些领域要先进得多。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接