cmd.exe(批处理)脚本中的数组、链表和其他数据结构
Posted
技术标签:
【中文标题】cmd.exe(批处理)脚本中的数组、链表和其他数据结构【英文标题】:Arrays, linked lists and other data structures in cmd.exe (batch) script 【发布时间】:2021-07-28 07:24:10 【问题描述】:我在玩 cmd.exe,但在它的帮助中我没有找到任何信息,如何定义数组。
我找到了,如何定义简单的变量:
set a=10
echo %a%
但是,我想创建数组、链表等...
那么,在 cmd.exe 中是否可以(我的意思是:在 cmd.exe 中是否存在任何数组关键字?)
我想实现一些算法为:
冒泡排序 快速排序 gnome 排序等等……
那么,我也想知道,Cmd.exe 有引用或实例、结构等吗?
因为它的帮助没有完全在:/?
Cmd.exe 是否可以通过图灵机定义定义为完整的? (图灵完备)
【问题讨论】:
@MatteoItalia linux shell有,powershell(基于.net)有,不知道windows CScript.exe有没有? 这应该很有趣,但实际上很可悲:尽管大多数答案没有回答问题,但还是得到了 13 票赞成。在我看来,仅仅用“白痴”、“非常骇人听闻”、“离奇”和类似词语来批评这个话题的答案根本没有任何价值。请注意,我不是批评那个答案,也不是为 Batch 辩护。我只是看不出该答案的所谓优点是值得如此多的赞成!我想知道对 Power Shell、VBS 脚本、Phyton、Ruby、Pearl、php 等主题的类似批评答案是否会收到类似数量的赞成票...... :( 说得好,阿西尼。重要的是要记住,像这样的问题涵盖了许多难以找到答案的简单用例,您的回答本着 SO 的原始精神回答了我的几个问题。 @meagar:我不明白你的意思......我知道所有这些编程语言都非常不同,并且每个都有自己的应用领域,追随者、批评等,但他们都应该值得来自科学家(程序员)社区的尊重。然而,在这个网站上,与对其他编程语言的批评相比,对批处理文件的批评非常频繁。最糟糕的是,这种批评经常被赞成,所以我只是想知道:为什么?也许不喜欢批处理文件的人比其他人更多,或者不那么尊重…… @Aacini:所以有人不同意你的观点;多么令人震惊!你会发现这在生活中经常发生。尤其是当你持有少数意见时。所以你认为 Batch 是一种可爱的语言。伟大的!你有权发表这种意见。其他人有权认为它不是。你不必惊慌。 【参考方案1】:好的。我会尽量说清楚,以免被误解……
在 Windows 批处理文件中,变量名应该以字母开头,并且可以包含任何有效字符,其中有效字符是:#$'()*+,- .?@[]_`~ 除了字母和数字。
这意味着从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
这样,echo %elem[2]%
将显示Second one
。
如果你想使用另一个变量作为索引,你必须知道百分号包围的变量被它们的值替换是从左到右;这意味着:
set i=2
echo %elem[%i%]%
没有给出想要的结果,因为它意味着:显示elem[
变量的值,然后是i
,然后是]
变量的值。
要解决这个问题必须使用延迟扩展,即在开头插入setlocal EnableDelayedExpansion
命令,索引变量用百分号括起来,数组元素用感叹号括起来: p>
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 内更改索引时将值存储在数组元素中: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!]%%
)
实现上述过程的另一种方法是使用额外的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]!
)
这样,批处理文件表现就像它管理数组一样。我认为这里的重点不是讨论 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%
注意索引值不限于数字,可以是任何包含有效字符的字符串;这一点允许定义在其他编程语言中称为associative arrays 的内容。在this answer 有详细说明使用关联数组解决问题的方法。另请注意,空格是变量名中的有效字符,因此您必须注意不要在变量名中插入可能被忽视的空格。
我详细说明了我必须在 this post 的批处理文件中使用数组表示法的原因。
在this post 中有一个批处理文件,它读取一个文本文件并将行的索引存储在一个向量中,然后根据行内容对向量元素进行气泡排序;等效结果是对文件内容进行排序。
在this post 中有一个基于存储在文件中的索引的基本关系数据库应用程序。
在this post 中有一个完整的 Batch 中的多链表应用程序,它组装从子目录中获取的大型数据结构并以 TREE 命令的形式显示。
【讨论】:
无耻的自我推销:this answer 演示了Array.splice()
的批量实现(这也依赖于您推荐的array[n]
命名约定)。
我认为除了=
&<NUL>
(0x00) 之外的任何字符都是有效的变量名,甚至是换行符
@HackingAddict1337:看来你是对的。但是,某些字符(如冒号)会阻止 访问 变量值。例如:set "var:=value"
和 echo %var:%
有效,但如果在冒号后插入任何字符,则无法访问变量值(因为 %var% sustitution-in-expansion 规则)【参考方案2】:
Windows shell 脚本确实不是为处理数组而设计的,更不用说复杂的数据结构了。在大多数情况下,Windows shell 中的所有内容都是字符串,但是,您可以做一些事情来“处理”数组,例如使用循环声明 n
变量 VAR_1, VAR_2, VAR_3...
并过滤前缀 VAR_
,或创建一个分隔字符串,然后使用 FOR
构造迭代分隔字符串。
同样,您可以使用相同的基本思想来创建类似结构的变量集,例如 ITEM_NAME, ITEM_DATA
或 w/e。我什至发现this link 谈到在 CMD 中模拟关联数组。
归根结底,这一切都非常骇人听闻和不便。命令行 shell 并不是为繁重的编程而设计的。我同意@MatteoItalia 的观点——如果您需要认真编写脚本,请使用真正的脚本语言。
【讨论】:
你认真下是什么意思? cmd.exe能否被图灵机定义为full? @magesi CMD 确实有一件事情要做——FOR
命令。如果您真的想学习 CMD,请掌握它并继续前进。
@trutheality 或者有一种基于 NT4 源代码编写自己的 cmd.exe 的方法,可以 :) 并包含一些新功能 :)
@trutheality 谢谢! :) 如果我把结果放在这里,我可以打电话给你看吗? :)
@magesi:一个更有用但足够疯狂的工作是对批处理语法的每一个奇怪位进行逆向工程(我认为即使在微软也没有官方规范)并修复@987654328 @来自 Wine 项目。 :)【参考方案3】:
我不久前使用伪数组批量实现了冒泡排序。 不知道为什么要使用它(尽管我承认在另一个批处理文件中这样做),因为随着列表大小的增加,它变得非常慢。更多的是给自己设定一个小挑战。 有人可能会觉得这很有用。
:: 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
【讨论】:
对不起。我不喜欢您的“伪数组”参考。数组主要是一个概念:一组通过索引选择的同名元素。你的程序可以管理一个数组,也可以不管理;没有这种“伪数组”的东西。有关详细信息,请参阅我的previous link... @Aacini:绝对有这样的事情。当您使用一种在语法或语义中不提供数组构造的语言来模拟数组时,这些语言可能会被明确无误地称为“伪数组”。 @PreferenceBean:对不起。你知道set /A
命令吗?在此示例中:set /A resul=9+6
,您将如何调用存储在resul
变量中的15
字符串? “伪数”? “模拟整数”?请记住,批处理文件不提供数字变量!
@Aacini:你知道吗,你一直使用的“对不起”是粗鲁的。我敢肯定这是无意的。但是您不必对 MS Batch 如此防御。
@Aacini:我对set /A
知之甚少,我也没有对 Batch 提出任何要求。我只是说,世界上肯定有有这样的概念,如“伪数组”。 Dave 所描述的似乎是一组 PHP 变量 $var1 = 0; $var2 = 1; $var3 = 2; $i = 2; echo $var$i;
。那是一个数组吗?不。这是模拟数组的尝试吗?是的。这是一个伪数组。这就是“伪”的意思。【参考方案4】:
说真的:我从来没有听说过批处理有数组,也许你可以用一些奇怪的技巧来模拟它们,但我认为这不是一个好主意。
引用/实例/结构是真实语言的东西,cmd 脚本只是在非常原始的解释器 command.com 上发展起来的一堆扩展,你可以做一些基本的脚本,但比一堆更复杂的东西对其他命令的调用注定会变得丑陋和难以理解。
唯一的“高级”构造是全能的怪人for
循环,它与变量替换的奇怪“规则”混合在一起(%var%
、%%var
、!var!
,是不同的东西由于愚蠢的解析器),使编写甚至是微不足道的算法成为奇怪黑客的集合(参见例如这里an implementation of quicksort)。
我的建议是,如果您想以理智的方式编写脚本,请使用真正的脚本语言,并保留批处理以实现简单、快速的 hack 和向后兼容性。
【讨论】:
msfn.org/board/topic/47265-making-arrays-in-batch 这里是示例 这不是一个数组,它是一个包含点分隔值的单个字符串变量,用for
循环分割。 set
/for
hack 的集合,正如我所说。在这种情况下你会做任何严肃的事情吗?
是的,for
命令与您将得到的差不多。和它一起工作是多么痛苦。
@magesi:也许吧,但是用一种连基本工具都没有的语言工作有什么好处呢?
@MatteoItalia 只是和它发生了有趣的性关系,就像在 Brainf*ck 中一样,我当然不会在上面发展,只是为了疯狂的乐趣【参考方案5】:
关于此声明:
我找到了,如何定义简单的变量:
set a = 10 echo %a%
这是完全错误的!变量a
将保持为空(假设它最初是空的)并且echo %a%
将返回ECHO is on.
一个名为a
SPACE 的变量实际上将设置为值SPACE kbd>10
.
因此,要使代码正常工作,您必须去掉等号周围的 SPACEs:
set a=10
echo %a%
要使分配对所有字符安全,请使用带引号的语法(假设您启用了command extensions,无论如何这是 Windows 命令提示符的默认设置):
set "a=1&0"
echo(%a%
对于您所有其他问题,我建议您阅读Aacini 的伟大而全面的answer。
【讨论】:
【参考方案6】:以下程序模拟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
)
还可以将程序参数存储在“数组”中,或者使用“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
【讨论】:
【参考方案7】:TLDR:
我想到了使用“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
下面我们制作了一些 Dumb Pseudo Arrays 和一个手动排序的 Pseudo Array,并创建了一个 Ordered Pseudo Array 来捕获 DIR 命令的输出。
我们还采用 Dumb Pseudo Arrays 并将它们转换为 Ordered 数组(之后删除原始 Dumb Pseudo Array 变量)。
然后我们手动更新所有有序数组以包含更多元素。
最后,我们通过对值 7 到 9 执行预定义的 For L 循环来动态报告数组中的一些值,并生成一个随机值以打印数组的第 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 脚本中实现了一种穷人结构或对象数组(字符串)。
IE 结构将是一个具有多个属性的“站点对象”,这些属性本身可能是具有子属性的对象。由于 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 为单位维护每个子文件夹和站点的运行总计,并在汇总给定目录的所有目录后显示要报告的值文件夹等动态。
虽然我不得不对其进行一些编辑,因为这也是这些版本的早期版本,但我认为这是最能展示其好处的实例之一。如果我在我的其他脚本之一中找到更好的示例,我可能也会在那里更新它。
【讨论】:
【参考方案8】:论“批处理编程中的图灵完备性”
是的,据我所知,Batch 是图灵完备的(如果您忽略“无限”功能,例如无限内存和计算时间;因此有人可能会争辩说,Batch 只是“理论上的图灵等效”)。
有所有基本的布尔和算术运算符以及循环 (for
) 和分支 (if
)。还有一个goto
功能允许建模循环(while
/do while
/for
)和sub routines
。块的嵌套是可能的。 Variables
可以命名、存储、删除/清除、显示/写入文件。 halt
条件可以通过 exit
(或 goto eof)达到。
作为旁注:可以从批处理程序中编写批处理文件,将其写入磁盘并运行它(允许自我修改/自定义/子例程/状态保存和恢复)。
但是没有无限的内存存储。在计算中只能使用 32 位算术。显然,运行批处理文件的计算机也有硬件和物理限制(只有有限的时间、速度或空间)。
需要注意的是,您提到的所有“高级”概念都不是“批处理编程语言”的一部分。没有集成类、对象、记录/结构、数组、链表、堆栈、队列等的概念。也没有提供任何默认算法,如排序等(除非考虑到带有管道的sort
或findStr
、more
等)。使用 %RANDOM%
变量进行随机化也是非常基本的。
如果您需要这些概念,您需要自己使用我上面提到的给定基本语言元素对它们进行建模(或者为此使用一些库/第三方批处理文件)。
当然,call
不仅可以处理批处理文件,还可以处理计算机上的任何补充程序,然后返回批处理执行(通过文件、标准 I/O 流或退出/错误级别代码进行通信)。这些程序可以用更方便的方式提供这些东西的高级语言编写。
在我看来,Bash
(Linux) 和 Powershell
(Windows/Linux) 在这些领域要先进得多。
【讨论】:
【参考方案9】:通用数组处理脚本
@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
模式:
[Def
] 定义、修改或清除数组。
[Def
] 开关:[/A:Groupname
] [/F:Filepath.ext
] [/D
] [/O:Index#Arg
] [/E:Element Sub value
] "element0"
~ "element#"
[Sort-int
] 使用/L
或/H
开关按最小值或最大值对数组进行排序
[Sort-int
] 开关:
[/A:Groupname
] [/N:New Groupname
] [/L
|/H
] [/D
]
[Sort-str
]
使用字母数字顺序对数组或文本文件字符串值进行排序
排序:[0-9][a-z]
[Sort-str
] 开关:
[/A:Groupname
] [/F:Filepath.ext
] [/D
]
[Find-searchstring
]
在数组中搜索提供的字符串值。
[Find-searchstring
] 开关:[/A:Groupname
]
【讨论】:
【参考方案10】:我以前使用过的一种方法是将文件用作数组,将文件夹用作数组的字典。
现在听我说完 - 一开始对你来说可能很愚蠢,但它有一些优点。
这个想法是文件可以被视为一个数组,甚至可以通过FOR
命令支持本机、易于使用的数组迭代。
array.txt
these are items in an array
对于二维数组,您可以使用上述文件的文件夹。 (名称如 0.txt
到 100.txt
)。请记住,您可能需要有一个单独的文件来索引它们,因为数组目录不一定按照您在 for 循环中所期望的方式排序,实际上更像是一个哈希映射,它是 string -> [string]
。
或者,我确信解析 csv 不会太难(记住字符串值中的逗号和制表符!;))
对于混合数组(其中有些项目是其他数组,有些是字符串),您可以将文件格式化如下:
complex-array.txt
"value "1 "2 \path.txt \path2.txt
还有一个像这样的文件夹:
complex-array\path.txt
complex-array\path2.txt
如果一行以一个字符开头,它是一个值,而另一个,它是一个路径(可能相对于这个文件)。当然,这可以是递归的。
不过有一个大问题。该脚本留下了(可能)需要在每次运行之前清理的文件。 (我之前说过,因为假设在此脚本运行时不会拔掉计算机是不安全的)。
我不确定这对性能的不利影响,而且批处理无论如何都很慢,所以也许这无关紧要。 (我相当肯定变量名修改策略更快,因为值会在内存中保留更长时间)
【讨论】:
【参考方案11】:@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%]!
【讨论】:
以上是关于cmd.exe(批处理)脚本中的数组、链表和其他数据结构的主要内容,如果未能解决你的问题,请参考以下文章