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
在下面,我们制作了一些哑伪数组和手动有序伪数组,并创建了一个有序伪数组来捕获DIR命令的输出。
我们还将采用哑哑伪数组并将其转换为有序数组(之后删除原始的哑哑伪数组变量)。
然后,我们更新所有有序数组以手动包含更多元素。
最后,我们通过对值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
公平地讲,此脚本示例可能无法非常清楚地显示正在发生的事情,因此我不得不即时进行更改以修复新的对象样式,但实际上:它创建连接对象,然后动态扩展它们以包括sub文件夹,并维护每个子文件夹和站点的运行总计(以KB,MB和GB为单位),并动态汇总给定文件夹的所有目录等后,报告要报告的值。
虽然我不得不对其进行一些编辑,因为这也是它们的较早版本,但我认为这是最能体现其优势的实例之一。如果我在其他脚本之一中找到了更好的示例,则也可以在那里进行更新。