cmd.exe(批处理)脚本中的数组,链接列表和其他数据结构

问题描述 投票:57回答:8

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

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

set a=10
echo %a%

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

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

我想实现一些算法:

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

等等...

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

导致其帮助不完整:/?

可以通过图灵机定义将Cmd.exe定义为完整吗? (图灵完成)

arrays windows batch-file cmd delayedvariableexpansion
8个回答
155
投票

好。我会尽量保持清醒,不要被误解......

在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]!。你必须使用!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!]%%
)

实现上一过程的另一种方法是使用额外的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中有一个批处理文件,它读取文本文件并将行的索引存储在向量中,然后根据行内容执行Buble Sort of vector元素;等效的结果是文件内容的排序。

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

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


8
投票

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

类似地,您可以使用相同的基本思想来创建类似于结构的变量集,例如ITEM_NAME, ITEM_DATA或w / e。我甚至发现this link谈论模拟CMD中的关联数组。

归结为它,这一切都非常严重和不方便。命令行shell只是不适合繁重的编程。我同意@MatteoItalia - 如果您需要严肃的脚本,请使用真正的脚本语言。


6
投票

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

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

唯一的“高级”构造是全能的怪人for循环,它与变量替换的奇怪“规则”(%var%%%var!var!,因为愚蠢的解析器是不同的东西)混合,使得写作甚至琐碎算法一组奇怪的黑客(参见例如an implementation of quicksort)。

我的提示是,如果您想以理智的方式编写脚本,请使用真正的脚本语言,并将批处理留给简单,快速的黑客和向后兼容。


6
投票

我刚才使用伪数组批量生成了冒泡排序。不确定为什么要使用它(虽然我会承认在另一个批处理文件中这样做),因为随着列表大小的增加它变得非常慢。更多的是为自己设置一个小挑战。有人可能会觉得这很有用。

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

5
投票

关于这个声明:

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

set a = 10
echo %a%

这完全是错的!变量a将保持为空(假设它最初为空)并且echo %a%将返回ECHO is on.名为aSPACE的变量实际上将设置为值SPACE10

因此,要使代码工作,您必须摆脱等号后面的空格:

set a=10
echo %a%

要使分配对所有字符都安全,请使用带引号的语法(假设您启用了command extensions,这是Windows命令提示符的默认设置):

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

对于你提出的所有其他问题,我建议阅读Aacini伟大而全面的answer


2
投票

以下程序模拟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

0
投票

TLDR:

我想到了使用“For”循环和“set”命令以允许解析变量,允许我创建Pseudo Arrays,包括有序和链表样式,更重要的是,Pseudo Objects类似于结构。

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

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和一个手动排序的伪数组,再创建一个有序伪数组来捕获DIR命令的输出。

我们还采用Dumb Pseudo Arrays并将它们转换为Ordered数组(之后删除原始的Dumb Pseudo Array变量)。

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

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

注意:

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

我指出这一点,因为它应该很容易看到我们如何从有序数组到Pseudo对象进行小跳转。

@(
 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的事情,一个带有增量计数器的简单变量行,手动或通过快速变量列表中的简单循环分配它们。

对于小型2-D阵列来说这很好。

但是我发现长数据阵列很痛苦,特别是当我需要多值内容时。

更不用说当我需要动态匹配和填充那些多维数组中的内容时,那里的简单用法就会崩溃。

我发现当你最终需要更新或添加功能的多个信息阵列时,它变得很难。

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

例如,您需要登录多个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这些并且很难记住哪一个是通过查看一个长字符串的末尾并复制它们等等。
  • 您停止添加更多路径,但有时您必须删除旧路径,否则当它们消失时会导致问题,如果您忘记更新站点罪行的总数,您可能会错过在某些路径上运行脚本。
  • 当添加或删除目录时,您必须为每个站点添加/删除它,这使得更难以使用订购,并且更容易错过站点,因为它们不容易ID。

只是,多么痛苦,甚至当你需要一组动态物体时,这都是手动的。

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

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

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

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

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为单位维护每个子文件夹和站点的运行总计,以及动态地总结给定文件夹等的所有目录后要报告的值中的哪些值。

虽然我不得不对它进行一些编辑,因为这也是这些版本的早期版本,但我认为这是最有可能显示其优点的实例之一。如果我在其他一个脚本中找到一个更好的例子,我也可以在那里更新它。


-3
投票
@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%]!
© www.soinside.com 2019 - 2024. All rights reserved.