web-dev-qa-db-fra.com

Tableaux, listes chaînées et autres structures de données dans le script cmd.exe (batch)

Je jouais avec cmd.exe, mais dans son aide, je ne trouvais aucune information sur la définition des tableaux.

J'ai trouvé comment définir des variables simples:

set a=10
echo %a%

Mais, je veux créer des tableaux, une liste chaînée, etc.

Alors, est-il capable dans cmd.exe?

Je veux réaliser des algorithmes comme:

  • sorte de bulle
  • tri rapide
  • sorte de gnome

etc...

Donc, je veux aussi savoir si Cmd.exe a des références ou des instances, des structs, etc.

Parce que son aide n'est pas complète dans: /?

Cmd.exe pourrait-il être défini comme étant complet par la définition de Turing-Machine? (Turing-Complete)

56
user1131997

D'accord. Je vais essayer d'être le plus clair possible pour ne pas être mal compris ...

Dans les fichiers batch Windows, un nom de variable doit commencer par une lettre et peut inclure tout caractère valide, où caractères valides sont: # $ '() * +, -.? @ [] _` {} ~ en plus des lettres et des chiffres.

Cela signifie que, du point de vue de cmd.exe, SET NORMAL_NAME=123 est identique à SET A#$'()*+,-.?@[\]_{}~=123 et identique à SET VECTOR[1]=123; les trois sont des variables normales. De cette façon, à vous de choisir _ d'écrire des noms de variables sous la forme d'éléments de tableau:

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

De cette façon, echo %elem[2]% affichera Second one.

Si vous souhaitez utiliser une autre variable comme index, vous devez savoir que le remplacement des variables entourées de symboles de pourcentage par leurs valeurs est analysé de gauche à droite; cela signifie que:

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

ne donne pas le résultat souhaité car cela signifie: affiche la valeur de la variable elem[, suivie de i, suivie de la valeur de la variable ].

Pour résoudre ce problème, vous devez utiliser expansion retardée, c'est-à-dire insérer la commande setlocal EnableDelayedExpansion au début, placer les variables d'index dans des symboles de pourcentage et placer les éléments du tableau entre des points d'exclamation:

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

Vous pouvez également utiliser les paramètres des commandes FOR comme index: for /L %%i in (1,1,3) do echo !elem[%%i]!. Vous devez utiliser! Index! pour stocker des valeurs dans des éléments de tableau lorsque l'index est modifié à l'intérieur d'un FOR ou d'un IF: set elem[!index!]=New value. Pour obtenir la valeur d'un élément lorsque l'index change dans FOR/IF, placez-le dans un double symbole de pourcentage et faites précéder la commande de call. Par exemple, pour déplacer une plage d’éléments de tableau de quatre emplacements vers la gauche:

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

Une autre manière de réaliser le processus précédent consiste à utiliser une commande FOR supplémentaire pour modifier le développement retardé de l'index par un paramètre remplaçable équivalent, puis à utiliser le développement retardé de l'élément de tableau. Cette méthode est plus rapide que l'appel précédent:

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

De cette façon, le fichier batch se comporte comme s'il gère les tableaux. Je pense que le point important ici n’est pas de savoir si Batch gère ou non les tableaux, mais le fait que vous pouvez gérer des tableaux dans des fichiers Batch de la même manière que d’autres langages de programmation.

@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%

Notez que les valeurs d'index ne sont pas limitées à des nombres mais qu'elles peuvent être n'importe quelle chaîne contenant des caractères valides. Ce point permet de définir ce que d’autres langages de programmation sont appelés tableaux associatifs . À cette réponse , une explication détaillée de la méthode utilisée pour résoudre un problème à l'aide d'un tableau associatif. Notez également que l’espace est un caractère valide dans les noms de variable, vous devez donc veiller à ne pas insérer d’espace dans des noms de variable qui risquent de ne pas être remarqués.

J'ai expliqué les raisons pour lesquelles je dois utiliser la notation de tableau dans les fichiers batch à ce post .

Dans cet article , il existe un fichier batch qui lit un fichier texte et stocke les index des lignes dans un vecteur, puis effectue un tri Buble des éléments vectoriels en fonction du contenu de la ligne; le résultat équivalent est un tri sur le contenu du fichier.

Dans cet article , il existe une application de base de base de données relationnelles dans Batch basée sur des index stockés dans des fichiers.

Dans this post , il existe une application complète à listes chaînées multiples dans Batch qui assemble une structure de données volumineuse extraite d’un sous-répertoire et l’affiche sous la forme de commande TREE.

147
Aacini

Les scripts Windows Shell ne sont vraiment pas conçus pour fonctionner avec des tableaux, sans parler des structures de données complexes. Pour l'essentiel, tout est une chaîne dans le shell Windows, mais vous pouvez effectuer certaines opérations pour "travailler avec" des tableaux, comme déclarer des variables nVAR_1, VAR_2, VAR_3... à l'aide d'une boucle et filtrer le préfixe VAR_ ou créer une chaîne puis en utilisant la construction FOR qui itère sur une chaîne délimitée.

De même, vous pouvez utiliser la même idée de base pour créer un ensemble de variables de type struct, tel que ITEM_NAME, ITEM_DATA ou w/e. J'ai même trouvé ce lien qui parle de la simulation d'un tableau associatif dans CMD.

Tout cela est terriblement maladroit et peu pratique. Le shell en ligne de commande n’était tout simplement pas conçu pour une programmation lourde. Je suis d'accord avec @MatteoItalia - si vous avez besoin d'un script sérieux, utilisez un vrai langage de script.

7
trutheality

Il y a quelque temps, j'ai mis en œuvre une sorte d'implémentation en mode batch des bulles utilisant des pseudo-tableaux . Je ne sais pas pourquoi vous l'utiliseriez (bien que je l'admette dans un autre fichier batch) car elle devient assez lente à mesure que la liste augmente . C’était plus pour me lancer un petit défi . Quelqu'un pourrait trouver cela utile.

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

Sérieusement parlant: je n'ai jamais entendu dire que ce lot contient des tableaux, vous pouvez peut-être les imiter avec un truc étrange, mais je n'appellerais pas cela une bonne idée.

Les références/instances/structures sont utiles dans un langage réel, le script cmd n’est qu’un ensemble d’extensions développées sur l’interpréteur très primitif qui était command.com; vous pouvez effectuer quelques scripts de base, mais rien de plus compliqué que de nombreux appels à d'autres commandes sont condamnées à devenir laides et incompréhensibles.

La seule construction "avancée" est la boucle for weirdo do-it-all, qui, mélangée aux étranges "règles" de substitution de variable (%var%, %%var, !var!, est différente à cause de l'analyseur idiot), rend l'écriture même triviale algorithmes une collection de hacks étranges (voir par exemple ici pour une implémentation de quicksort ).

Mon astuce est la suivante: si vous souhaitez effectuer vos scripts de manière saine, utilisez un langage de script real et laissez batch pour des hacks simples et rapides ainsi que pour une compatibilité ascendante.

6
Matteo Italia

Concernant cette déclaration:

J'ai trouvé comment définir des variables simples:

set a = 10
echo %a%

C'est tout simplement faux! La variable a restera vide (en supposant qu'elle était initialement vide) et echo %a% retournera ECHO is on. Une variable appelée aSPACE sera effectivement mis à la valeur SPACE10.

Donc, pour que le code fonctionne, vous devez vous débarrasser de la SPACEs autour du signe égal à:

set a=10
echo %a%

Pour sécuriser l'affectation contre tous les caractères, utilisez la syntaxe citée (en supposant que vous avez le extensions de commande activé, ce qui correspond par défaut à l'invite de commande Windows):

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

Pour tout le reste de votre question, je vous recommande de lire Aacini est génial et complet réponse .

5
aschipfl

Le programme suivant simule des opérations de vecteurs (tableaux) dans cmd. Les sous-routines présentées étaient initialement conçues pour des cas particuliers, tels que le stockage des paramètres de programme dans un tableau ou la lecture en boucle de noms de fichiers dans une boucle "for", puis leur stockage dans un tableau. Dans ces cas, dans un bloc enabled delayed expansion, les caractères "!" - s'ils sont présents dans les valeurs des paramètres ou dans la valeur de la variable de boucle "for" - sont interprétés. C'est pourquoi, dans ces cas, les sous-routines doivent être utilisées dans un bloc 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
)

Il est également possible de stocker les paramètres du programme dans un "tableau" ou de parcourir les noms de fichiers dans un répertoire à l'aide d'une boucle "for" et de les stocker dans un "tableau" (sans interpréter "!" dans leurs valeurs) sans utiliser le paramètre présenté. sous-programmes du programme ci-dessus:

@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
user5280669

TLDR:

J'ai eu l'idée d'utiliser une boucle "For" et la commande "set" pour permettre l'analyse syntaxique des variables, ce qui m'a permis de créer des pseudo tableaux, à la fois ordonnés et style liste chaînée, et plus important encore, des pseudo-objets s'apparentant à des structures.

Un pseudo-tableau batch typique, et comment analyser:

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

Ci-dessous, nous fabriquons des pseudo tableaux matriciels et un pseudo tableau commandé manuellement, ainsi que de créer un pseudo tableau ordonné capturant le résultat d'une commande DIR.

Nous prenons également les tableaux pseudo-muets Dumb et les convertissons en tableaux ordonnés (en supprimant les variables de type pseudo-tableaux Dumb après).

Nous mettons ensuite à jour tous les tableaux ordonnés pour qu'ils contiennent plus d'éléments manuellement.

Enfin, nous rapportons dynamiquement certaines des valeurs du tableau en effectuant une boucle For L prédéfinie pour les valeurs 7 à 9 et en générant une valeur aléatoire pour imprimer le 4ème exemple de valeur du tableau.

Remarque:

Je crée une variable pour contenir la méthode d’ajout de membres afin de faciliter leur ajout.

Je souligne cela car il devrait être facile de voir comment nous faisons le saut mineur des tableaux ordonnés aux pseudo-objets.

@(
 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

Exemple de résultats:

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"

Au départ, je ferais des choses semblables à Aacini, une simple ligne de variables avec un compteur incrémental, manuellement ou en les affectant via une simple boucle à partir d'une liste rapide de variables.

C'était bien pour les petits tableaux à deux dimensions.

Cependant, je trouve cela pénible pour de longs tableaux de données, en particulier lorsque j'ai besoin d'un contenu multivaleur.

Sans parler du moment où je dois faire correspondre et renseigner le contenu dans ces tableaux multidimensionnels de manière dynamique, là où l'utilisation simple s'effondre.

J'ai constaté que cela devenait difficile lorsque vous finissiez par avoir besoin de plusieurs tableaux d'informations que vous deviez mettre à jour ou ajouter des fonctionnalités.

En tant que tel tableau est essentiellement une liste de sous-chaînes dont vous avez besoin pour exporter en tant que variables, et ajouter ou modifier leur ordre signifie modifier votre code.

Prenons par exemple un scénario dans lequel vous devez vous connecter à plusieurs serveurs FTP et supprimer des fichiers datant de plus de X jours de certains chemins.

Initialement, vous pouvez créer des tableaux simples de sous-chaînes que je définirai comme ceci:

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

Ou comme indiqué dans cet exemple de code.

(
  SETOCAL
  ECHO OFF

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

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

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

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

Maintenant, 13 sites, ce n'est pas si mal, je suis sûr que vous dites. droite? Vous pouvez simplement en ajouter un à la fin, puis mettre les informations et c'est fait.

Ensuite, vous devez ajouter les noms des sites dans le rapport. Vous devez donc ajouter un autre terme à chaque chaîne située à la 5ème position, de sorte que vous n’ayez pas à changer de fonction.

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

Ensuite, vous réalisez que vous devez les garder en ordre en fonction de leurs noms de site (ou de leurs adresses IP, mais la plupart des gens se souviendront plus facilement de ces noms et vous devez être en mesure de laisser les autres jeter un œil) afin de pouvoir modifier l'ordre dans lequel vous le souhaitez. l’ensemble des 13 spots, l’appel pour développer les variables et la fonction.

::...
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% "
::...

Ensuite, ça ne fait qu'empirer:

  • Le nombre de répertoires que vous devez vérifier, en utilisant différents utilisateurs, sur le même site commence à augmenter.
  • Vous réalisez que vous avez besoin de durées de conservation différentes pour chaque site et, plus tard, pour chaque répertoire.
  • Vous en obtenez 30, 40,50 et il est difficile de vous rappeler lequel est lequel en regardant l'extrémité d'une longue chaîne et en la copiant, etc.

  • Vous avez cessé d’ajouter d’autres chemins, mais vous devez parfois supprimer les anciens ou des problèmes peuvent survenir, et si vous oubliez de mettre à jour le nombre total de sites sur la liste, vous risquez de ne pas pouvoir exécuter le script.

  • lorsqu'un répertoire est ajouté ou supprimé, vous devez l'ajouter/le supprimer de chaque site, ce qui rend plus difficile l'utilisation de la commande et facilite la perte de sites car ils ne sont pas faciles à identifier.

Juste, quelle douleur, et ce n'est pas même lorsque vous avez besoin d'un ensemble dynamique d'objets, tout cela est manuel.

Alors que peux-tu faire? Eh bien, voici ce que j'ai fait:

J'ai fini par avoir recours à la mise en place d'une sorte de structure ou de tableau d'objets (de chaînes) dans les scripts cmd, là où le besoin se présentait.

IE la structure serait un "objet de site" qui aurait plusieurs propriétés, qui pourraient être des objets avec des sous-propriétés elles-mêmes. Comme CMD n’est pas réellement orienté objet, c’est un peu un kludge, tout comme les tableaux.

Puisque l'exemple avec lequel j'ai commencé a fini par être le premier endroit où j'ai essayé ces derniers, vous pouvez voir cette étape intermédiaire de l'amalgame que je vais définir comme suit:

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

Pour résoudre le problème de la nécessité de réordonner des ensembles de données à la volée, j'ai envisagé d'utiliser une forme de liste chaînée avec laquelle je m'amusais, mais comme je voulais facilement ajouter des éléments à chaque groupe de sites tout en conservant l'ordre entre les sites, j'ai opté pour un méthode simple.

Voici un autre exemple de code de cette étape d'utilisation:

@(
    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

Comme vous pouvez probablement le constater, ces structures fonctionnent très bien lorsque vous avez des ensembles de données hiérarchiques à forger que vous devez appliquer manuellement et afficher les données dans un ordre séquentiel spécifique. Bien que, bien sûr, aujourd’hui, j’applique le nom du script à la base des structures, car j’estime que cela est plus utile et qu’il est possible d’utiliser des tableaux ordonnés en fonction des besoins. .

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

@(
    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

Bien que je devais le modifier un peu, car il s’agit également d’une version antérieure de ces derniers, j’ai pensé que c’était l’un des cas où il pourrait au mieux montrer les avantages. Si je trouve un meilleur exemple dans l'un de mes autres scripts, je pourrai également le mettre à jour.

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

0
Ben Personick