Apprendre la programmation de script batch

Juillard Jean-Baptiste

Ce cours a pour but de vous apprendre la programmation de script batch. Tombés en désuétude, ils reviennent forts depuis Windows Server 2012 du fait que celui-ci utilise une interface en ligne de commande aussi appelée CLI (Command Line Interface) ou CUI (Console User Interface).

Commentez

- Titre : Apprendre la programmation de script batch

- Auteur : Juillard Jean-Baptiste

- Parution : 13 avril 2015

- Public visé :

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2015 Juillard Jean-Baptiste. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- Remerciements

- I. Introduction

- I-A. L'éditeur de texte

- I-B. Encodage des caractères

- I-C. Hello World

- I-D. Différence entre la commande « rem » et le préfixe « :: »

- I-E. Les échappements de caractères

- I-F. Les bogues de la commande « rem » et du préfixe « :: »

- II. Les variables

- II-A. Les variables d'environnement

- II-B. La commande « set »

- II-C. Les variables de type chaîne de caractères

- II-D. Les variables de type nombre entier signé

- II-D-1. Les opérations arithmétiques

- II-D-2. Les opérations logiques

- II-D-3. Les opérateurs unaires

- II-E. Les nombres entiers signés en notation hexadécimale

- II-F. Les nombres entiers signés en notation octale

- III. Les conditions

- III-A. If, else, else if, not et /i

- III-B. La condition « exist <path> »

- III-C. La condition « defined <variable> »

- III-D. La condition « errorlevel <opérande> »

- III-E. La condition « cmdextversion <opérande> »

- III-F. La condition « <chaîne1> <comparateur> <chaîne2> »

- III-G. La condition « "<chaîne1>"=="<chaîne2>" »

- III-H. Mise en application de la commande « if »

- IV. Tout dépend du contexte

- IV-A. Les labels

- IV-B. Les sauts

- IV-C. Contexte de commande

- IV-C-1. La portée des variables

- IV-D. Les fonctions

- IV-D-1. L'appel de fonction

- IV-D-2. Sortie de fonction

- IV-E. Les paramètres de commande

- IV-E-1. Les modificateurs de paramètres de commande

- IV-E-2. La commande shift

- IV-F. Les sorties

- IV-G. Code de sortie

- IV-H. Script batch et adresse de retour

- IV-I. La commande start

- IV-J. La commande setlocal

- IV-K. La commande endlocal

- IV-L. Mise en application des contextes

- V. Les boucles

- V-A. Les boucles pour chemin d'accès

- V-B. Les boucles pour compteurs

- V-C. Les boucles de recherche

- V-D. Les échappements propres à la boucle for

- V-E. Mise en application des boucles

- VI. L'expansion retardée des variables

- VI-A. Cas de la boucle for

- VI-B. Générer dynamiquement des noms de variables

- VI-C. Parcourir une chaîne

- VI-D. Mise en application de l'expansion retardée

- VII. La gestion des flux

- VII-A. Le canal standard

- VII-B. Les opérateurs de redirection

- VII-C. L'opérateur &

- VII-D. La sortie standard

- VII-E. Les opérateurs > et >>

- VII-F. L'erreur standard

- VII-G. L'entrée standard

- VII-H. Le pseudo-périphérique NUL

- VII-I. L'opérateur |

- VII-I-1. Les filtres

- VII-I-2. Le filtre find

- VII-I-3. Le filtre sort

- VII-I-4. Le filtre more

- VII-I-5. Le filtre findstr

- VII-J. L'opérateur <

- VII-K. Le point sur « set /p » et « type »

- VII-L. Les opérateurs && et ||

- VII-L-1. Tester les deux états d'erreurs

- VII-M. Les opérateurs <& et >&

- VII-N. Les handles pour tampon utilisateur

- VII-O. Les pseudo-fichiers de périphérique

- VII-O-1. Le pseudo-fichier CON

- VII-O-2. Les pseudo-fichiers COMx

- VII-O-3. Les pseudo-fichiers LTPx

- VII-O-4. Le pseudo-fichier PRN

- VII-O-5. Le pseudo-fichier AUX

- VIII. Les fichiers de réponse

- VIII-A. La syntaxe des fichiers « *.ini »

- VIII-B. Lecture d'un fichier « *.ini »

- IX. Sécurité et script batch

- IX-A. Les variables

- IX-B. L'injection de commandes

- Annexe A. Les caractères

- A.1. Affichage des caractères de contrôle

- A.2. Insertion de caractères de contrôle et de signaux

Remerciements

Je tiens à remercier gangsoleil et djibril pour leurs relectures techniques attentives ; ainsi que f-leb, FRANOUCH et milkoseck pour leurs relectures orthographiques assidues.

I - Introduction

Les fichiers batch sont des scripts de commandes qui s'exécutent dans l'interpréteur de commande Windows. Alors quelle pourrait être l'utilité d'écrire ces lignes de commande dans un script ? En fait, il y a plusieurs raisons :

- éviter d'avoir à réécrire sans cesse les mêmes commandes lors de tâches répétitives ;

- possibilité de créer de vrais petits « programmes » facilitant les tâches qui doivent être réalisées via l'interpréteur de commande.

Nous allons étudier les méthodes permettant de concevoir ces programmes. Dans la suite de ce document, nous utiliserons la convention suivante :

- les termes « interpréteur de commande » ou « interpréteur » désignent l'exécutable « cmd.exe » ;

- dans les syntaxes de commande, les parties encadrées avec les caractères [ et ] sont optionnelles, les parties encadrées avec les caractères < et > sont à remplacer par différentes informations à fournir (celles-ci seront précisées avec la syntaxe) et les parties encadrées avec les caractères { et } sont des parties à choix multiple où chaque choix est séparé des autres avec le caractère |.

La convention syntaxique est la même que celle pour l'aide en ligne de commande, ainsi il vous sera plus facile de la comprendre.

I-A - L'éditeur de texte

Un simple éditeur de texte, tel que le notepad fourni avec Windows, s'avère peu pratique pour écrire des scripts batch. Pour exploiter pleinement les capacités de ces scripts, il faut pouvoir encoder les caractères non anglophones comme les accents ou le « ç » afin de pouvoir accéder aux fichiers dont les noms comportent ce genre de caractères exotiques. La plupart des éditeurs de texte encodent les fichiers avec le jeu de caractères de la norme ANSI qui correspond à la langue d'installation du système d'exploitation alors que l'interpréteur de commande utilise une étendue de page de code (aussi appelée Code Page) comme CP-850 qui est l'encodage par défaut de l'interpréteur pour les systèmes Windows installés en français (France).

Sous Windows, il y a un éditeur de texte que vous possédez peut-être déjà, c'est Notepad++ (la version 5.9.x ou supérieure est nécessaire) ; si ce n'est pas le cas, vous pouvez le télécharger ici : http://notepad-plus-plus.org/fr/

Dans ce dernier, vous pourrez sélectionner l'encodage dans le menu « Encodage > Codage de caractères », puis si vous voulez le français par exemple : sélectionnez « Langues d'Europe occidentale>OEM-850 ». Dans Notepad++, les code pages sont appelés OEM-XXX, au lieu de CP-XXX, mais ce sont les mêmes encodages. Il faut aussi noter que dans le même menu que OEM-850, il y a OEM-863 : français ; c'est bien du français, mais pour le Québec. Cette manipulation sera à effectuer pour chaque script afin d'utiliser le bon encodage, il n'est pas possible d'effectuer cette opération de façon automatique dans Notepad++.

I-B - Encodage des caractères

La gestion des pages de code dans l'interpréteur se fait via les commandes mode et chcp, ces commandes permettent d'afficher le code page utilisé ou de le modifier en utilisant l'une des syntaxes suivantes.

Définir le code page à utiliser (où <XXX> est le numéro de code page).

mode con cp select=<XXX>

chcp <XXX>

Afficher le code page utilisé.

mode con cp [/status]

chcp

Le tableau 1 fournit une liste non exhaustive des différents code pages utilisés par l'interpréteur de commande.

Code Page

Description

CP-437

pour le support des langues anglophones.

CP-720

pour le support des langues arabes.

CP-737

pour le support du grec.

CP-775

pour le support des langues baltes.

CP-850

pour le support des langues d'Europe occidentale (mis à jour par CP 858) dont le français (France), mais aussi l'allemand, le basque, le catalan, le danois, l'espagnol, le finnois, l'italien, le néerlandais, le norvégien, le portugais, le suédois, l'afrikaans, le faeroese, le flamand et l'irlandais.

CP-852

pour le support des langues d'Europe centrale.

CP-855

pour le support de l'alphabet cyrillique.

CP-857

pour le support du turc.

CP-858

pour le support des langues d'Europe occidentale, dont le français (France), il s'agit d'une mise à jour de 1998 basée sur CP-850 ou seul le symbole « € » a été ajouté au point de code 0xD5.

CP-861

pour le support de l'islandais.

CP-863

pour le support du français (Québec).

CP-864

pour le support des langues arabes.

CP-866

pour le support de l'alphabet cyrillique.

CP-869

pour le support du grec.

Tableau 1 : Code Page

Il faut noter que malgré la mise à jour de CP-850 par CP-858, l'encodage par défaut pour l'interpréteur sous Windows installé en français (France) reste CP-850. Il est donc préférable d'utiliser CP-850. La liste des code pages supportés par un système Windows est disponible dans le registre sous la clé : HKLM\SYSTEM\CurrentControlSet\Control\Nls\CodePage. Vous noterez la présence de 1252 dans la clé précédemment mentionnée, il s'agit de l'encodage Windows-1252 utilisé par les API Windows et donc par la plupart des blocs-notes sur les systèmes installés en français (France) ; cependant il n'est pas recommandé de l'utiliser dans l'interpréteur de commande du fait que les commandes ne sont pas faites pour le supporter, il peut même provoquer des bogues dans certains cas. L'encodage par défaut pour l'interpréteur peut être trouvé via la valeur OEMCP de type REG_SZ toujours dans la clé précédemment mentionnée.

Il est possible d'ajouter les Code Pages manquants (par défaut, seuls les code pages correspondant à la langue d'installation sont disponibles) en installant le pack de langue correspondant à l'encodage voulu. Chaque pack de langue comprend une table de conversion permettant à Windows de convertir un point de code en sa représentation graphique et donc de l'afficher. Il est aussi possible d'utiliser l'Unicode (UCS-2 seulement) avec les sorties de commandes internes de l'interpréteur pour permettre l'interaction avec les programmes qui utilisent Unicode de manière « native » (pour Perl par exemple) en appelant l'interpréteur via la commande suivante (où <commande> est la commande à exécuter).

cmd /u <commande>

Dans Notepad++, les caractères absents du clavier peuvent êtres rajoutés via le menu « Édition>Panneau des caractères ASCII » puis double-clic sur le caractère voulu.

I-C - Hello World

Nous allons commencer par le traditionnel « hello world », dont voici le code (Script 1). Copiez le code dans Notepad++, puis enregistrez-le avec l'encodage OEM-850 et l'extension « .bat » ou « .cmd » (seulement sur les systèmes Vista et supérieur).

La différence entre fichier « .bat » et fichier « .cmd » tient dans l'interpréteur de commande, en effet sur les systèmes Windows XP, il y avait deux interpréteurs de commande : « cmd.exe » et « COMMAND.COM ». Les fichiers avec l'extension « .bat » étaient exécutés par « cmd.exe » et les fichiers avec l'extension « .cmd » étaient exécutés par « COMMAND.COM », les deux types de fichiers avaient des spécificités différentes. Cependant sur les systèmes Windows Vista et supérieur, seul subsiste « cmd.exe », tous les scripts « .bat » et « .cmd » sont exécutés par « cmd.exe » et ont les mêmes spécificités.

Script 1

1. @echo off

2. cls

4. rem Ceci est une remarque.

5. :: Ceci est un commentaire.

7. echo Hello World !!!! Déjà là ?

8. echo.

9. pause

Lorsqu'on exécute ce script en cliquant dessus, on obtient l'affichage suivant.

Étudions la composition du script 1. La première ligne, @echo off, est déjà intéressante, elle est composée :

- du préfixe @ qui sert à inverser l'état de l'affichage standard ;

- de la commande echo qui sert à gérer l'affichage en ligne de commande ;

- et du paramètre off qui sert à désactiver l'affichage standard.

L'affichage standard définit ce que l'interpréteur de commande affiche par défaut. Par exemple lors du lancement de l'interpréteur de commande ci-dessous ; l'affichage standard renvoie le chemin d'accès du répertoire courant, soit C:\Users\Portable>.

Lors de l'exécution d'un script, l'affichage standard renvoie, par défaut, le chemin d'accès du répertoire courant suivi de la commande en cours d'exécution comme dans l'exemple ci-dessous.

Le préfixe @, quand il est placé en début de ligne, sert à inverser l'état de l'affichage standard (activé ou désactivé) pour l'exécution de la commande qui le suit (pas seulement pour la commande echo). Ce comportement prend fin une fois la commande exécutée. Ainsi la commande @cd /d "C:\Users\Portable\" ne sera affichée que si l'affichage standard est désactivé. La syntaxe du préfixe @ est la suivante (où <commande> est la commande à exécuter) :

@<commande>

La commande echo gère l'affichage dans l'interpréteur, elle peut :

- modifier l'état de l'affichage standard ;

- afficher l'état de l'affichage standard ;

- afficher un message ou une ligne vide dans l'interpréteur.

Désactiver l'affichage standard peut être fait via la syntaxe suivante (seuls les erreurs et les messages de la commande echo sont affichés).

echo off

Activer l'affichage standard peut être fait via la syntaxe suivante (tout est affiché).

echo on

Utilisée sans paramètre, la commande echo renvoie l'état de l'affichage standard en cours.

echo

Si l'on reprend le script 1, la ligne @echo off permet de désactiver l'affichage standard sans que la commande soit affichée. Sur la deuxième ligne du script 1, la commande cls est utilisée pour vider la fenêtre de l'interpréteur de son contenu, cette commande ne prend aucun paramètre. Sa syntaxe est donc la suivante.

cls

La ligne suivante du script 1 est vide, elle ne sera donc pas prise en compte lors de l'exécution du script permettant ainsi de le rendre plus lisible. La quatrième ligne est composée de la commande rem et d'une chaîne de caractères, cette commande permet d'insérer des remarques dans son script. Si et seulement si l'affichage standard est activé, la commande rem sera affichée. La syntaxe de la commande rem est la suivante (où <remarque> est la chaîne de caractères à insérer en remarque).

rem <remarque>

La cinquième ligne du script 1, :: Ceci est un commentaire., est composée du préfixe :: et d'une chaîne de caractères. Le préfixe :: définit la chaîne de caractères qui le suit comme étant un commentaire ; ce comportement prend fin au premier retour à la ligne. Quel que soit l'état de l'affichage standard, la chaîne de caractères préfixée par :: ne sera pas affichée. La syntaxe est la suivante (où <commentaire> est le commentaire à insérer).

::<commentaire>

Suit une autre ligne vide puis la commande echo Hello World !!!! Déjà là ? qui affiche Hello World !!!! Déjà là ? dans la fenêtre de l'interpréteur. La syntaxe suivante permet donc d'afficher un message même si l'affichage standard est désactivé (où <message> est le message à afficher).

echo <message>

Vient ensuite la commande echo. qui permet d'afficher la ligne vide que l'on voit dans l'affichage obtenu. Si un point suit directement la commande echo et qu'après le point il y a un retour à la ligne, celle-ci affiche une ligne vide.

echo.

Sur la ligne suivante se trouve la commande pause qui met en pause l'exécution du script jusqu'à ce que l'utilisateur appuie sur une touche quelconque du clavier, elle affiche le message Appuyez sur une touche pour continuer… (quel que soit l'état en cours de l'affichage standard) et ne prend aucun paramètre. Dans le script 1, cette commande permet de visualiser le contenu de la fenêtre de l'interpréteur avant que celle-ci ne se ferme.

pause

I-D - Différence entre la commande « rem » et le préfixe « :: »

Pour mieux comprendre la différence entre la commande rem et le préfixe ::, étudions le script 2.

Script 2

1. cls

2. rem Remarque 1

3. :: Commentaire 1

4. @echo off

5. rem Remarque 2

6. :: Commentaire 2

7. pause

Comme vous le voyez dans l'affichage du script 2, la commande rem Remarque 1, est présente à l'écran ; l'affichage standard étant activé, les commandes exécutées sont toutes affichées. La chaîne :: Commentaire 1 n'est pas affichée, c'est dû au fait que le préfixe :: n'est pas une commande et donc n'est pas renvoyé par l'affichage standard. Vient ensuite la commande @echo off qui désactive l'affichage standard sans que la commande ne soit affichée, suivie de la commande rem Remarque 2 qui n'est pas affichée (l'affichage standard étant à présent désactivé), pas plus que la chaîne :: Commentaire 2 qui est de toute façon exclue par l'affichage standard.

I-E - Les échappements de caractères

Les caractères spéciaux (aussi appelé méta-caractères) sont des caractères ayant une signification particulière pour l'interpréteur de commande. Chaque fois que l'interpréteur les rencontre, il leur applique le traitement correspondant, quelles que soient leurs positions dans la ligne de commande. En voici la liste :

& | ^ < > ( )

Pour pouvoir les utiliser dans une chaîne de caractères, il faut les « échapper » avec le caractère ^, c'est-à-dire placer ce caractère devant. Ainsi l'interpréteur saura qu'ils font partie d'une chaîne et qu'ils n'ont rien à voir avec la commande. Voir le script 3 comme exemple.

Script 3

1. @echo off

2. echo ^& ^| ^^ ^< ^> ^( ^)

3. pause

Il faut aussi noter que d'autres caractères peuvent avoir besoin d'être échappés, mais ils suivent d'autres règles. Le caractère % doit être échappé par lui-même pour être considéré comme faisant partie d'une chaîne lorsqu'il est utilisé dans un script comme le montre le script 4.

Script 4

1. @echo off

2. echo %%

3. pause

Un autre cas est à noter, il concerne les caractères " et \ qui doivent être échappés avec le caractère \ lorsqu'ils sont utilisés dans un paramètre entre guillemets. Dans le script 5, si les caractères " et \ n'avaient pas été échappés, le résultat ne serait pas celui attendu. L'interpréteur aurait compris qu'il y avait une chaîne "cmd /c "notepad et une chaîne "%cd%\%~nx0"", ce qui aurait conduit à une erreur d'évaluation de la commande.

Script 5

1. @echo off

2. runas /User:%UserName% "cmd /c \"notepad \"%cd%\\%~nx0\""

3. pause

Le caractère d'échappement ^ peut aussi être utilisé afin d'écrire une seule et même commande sur plusieurs lignes. Lorsque l'interpréteur rencontre le caractère ^ devant un retour à la ligne, il supprime ce caractère et le retour à la ligne, puis continue à lire les données fournies dans la ligne suivante. Exemple avec le script 6.

Script 6

1. @echo off

2. echo foo ^

3. bar

4. pause

D'autres échappements existent, cependant ils seront abordés plus loin dans ce document, car ils s'appliquent à des commandes précises et ne concernent pas les autres. Par exemple, la commande echo qui considère le point comme un échappement de la chaîne qui le suit. Lorsque la commande echo est directement suivie d'un point, elle considère la chaîne qui se trouve après le point comme une chaîne à afficher, retour à la ligne compris. Cela permet d'utiliser en début de chaîne un « mot » qu'elle aurait, dans le cas contraire, considéré comme un paramètre. Exemple avec le script 7.

Script 7

1. @echo off

3. echo Affichage d'une ligne vide sans le point:

4. echo

6. echo.

7. echo.

9. echo Affichage d'une ligne vide avec le point:

10. echo.

12. echo.

13. echo.

15. echo Affichage de la chaîne "     /?  Print this help." sans le point:

16. echo     /?  Print this help.

18. echo.

19. echo.

21. echo Affichage de la chaîne "     /?  Print this help." avec le point:

22. echo.    /?  Print this help.

24. echo.

25. echo.

26. pause

Dans l'affichage ci-dessous, on remarque que l'affichage d'une ligne vide sans le point renvoie l'état de l'affichage standard contrairement à celle avec un point. Si à la place d'un retour à la ligne, nous avions utilisé plusieurs espaces, le résultat aurait été le même, quel que soit le nombre d'espaces. En effet, l'interpréteur n'évalue pas le nombre d'espaces entre deux constituantes d'une ligne de commande, il aurait donc considéré qu'aucun paramètre n'est fourni. Il en est de même avec la chaîne /? Print this help., l'interpréteur considère la sous-chaîne /? comme un paramètre qui affiche l'aide en ligne de commande.

Le tableau 2 récapitule les différents échappements que l'on vient de voir.

Échappement

Description

^

Placé devant un des caractères spéciaux, soit &^|<>(), il signifie que le caractère qui le suit fait partie de la chaîne et qu'il n'a rien à voir avec la commande.

Placé devant un retour à la ligne, il signifie que la ligne suivante fait partie de la même commande.

%

Placé devant le caractère % dans un script, il signifie que le caractère qui le suit fait partie de la chaîne.

\

Placé devant un des caractères \ et " dans un paramètre entre guillemets, il signifie que le caractère qui le suit fait partie du paramètre entre guillemets.

.

Placé immédiatement après la commande echo, il signifie que la chaîne qui le suit est une chaîne à afficher et non un paramètre de la commande echo.

Tableau 2 : Les échappements

I-F - Les bogues de la commande « rem » et du préfixe « :: »

Un problème récurrent en batch scripting est que les remarques et commentaires (fournis via rem et ::) provoquent des bogues du fait d'une mauvaise utilisation. La commande rem et le préfixe :: prenant en paramètre une chaîne de caractères, ils attendent donc une chaîne de caractères valide. Ainsi les échappements doivent être utilisés pour les caractères suivants : & | ^ < > ( ) %.

II - Les variables

Les variables sont gérées via la commande set, il en existe deux types dont voici la liste :

- les variables de type chaîne de caractères ;

- les variables de type nombre entier signé.

Chaque type de variable est déclaré et traité différemment ; il est possible de les transtyper, c'est-à-dire de les passer d'un type à l'autre, à condition de respecter les règles suivantes :

- une chaîne de caractères ne pouvant être transtypée en nombre que si elle ne contient que des chiffres ;

- un nombre pourra toujours être transtypé en chaîne de caractères (qui ne contiendra que des chiffres).

Les variables sont accessibles via un identifiant qui leur est propre. Ainsi chaque fois qu'il sera fait mention de cet identifiant, l'interpréteur sera capable de fournir la valeur associée. Cela peut être fait en utilisant le caractère % de part et d'autre de l'identifiant, on parle alors d'expansion de la variable. Par exemple avec la variable PATH (qui est une variable d'environnement : fournie par le système), pour obtenir sa valeur ; il faudrait utiliser la syntaxe %PATH% comme dans le script 8.

Script 8

1. @echo off

2. echo %PATH%

3. pause

Ce qui produirait un affichage similaire à celui ci-dessous.

Les identifiants de variable ne sont pas sensibles à la casse, c'est-à-dire que l'interpréteur ne fait pas la différence entre majuscule et minuscule lorsqu'il expanse, crée ou modifie une variable. Ainsi les identifiants PATH, Path et path désignent une seule et même variable.

II-A - Les variables d'environnement

Nous allons commencer par les variables d'environnement, elles sont toutes de type chaîne de caractères. C'est le système d'exploitation qui définit leurs valeurs soit parce que d'un système à l'autre leurs valeurs peuvent être différentes, soit parce que ces variables sont définies dynamiquement lors de leur expansion. Cela permet d'accéder à des fichiers dont le chemin d'accès pourrait ne pas être le même d'une machine à l'autre ou de pouvoir obtenir, par exemple, l'heure ou la date dont les valeurs sont modifiées en permanence.

Dans le tableau 3, vous trouverez la liste de ces variables. Dans la première colonne, vous trouverez le nom de ces variables ; ces noms sont réservés par le système, et même s'il est possible de modifier leur valeur pendant l'exécution du script, les modifications apportées prendront fin en même temps que le script. De plus, dans certains cas, la modification de leur valeur peut entraîner une mauvaise évaluation de l'environnement par le script. La deuxième colonne donne la description de la valeur renvoyée par la variable. Et enfin, la troisième colonne donne la compatibilité en fonction de la version du système. Dans cette colonne, il est fait mention de « systèmes NT », il s'agit de la « famille » du système d'exploitation. Les systèmes NT sont des systèmes reposant sur un noyau de type NT provenant du système Windows du même nom. Cette famille comprend au jour où sont écrites ces lignes :

- les systèmes Windows NT X.Y (où X est la révision majeure et Y la révision mineure) ;

- les systèmes Windows 2000 ;

- les systèmes Windows XP ;

- les systèmes Windows Server 2003 ;

- les systèmes Windows Vista ;

- les systèmes Windows Server 2008 ;

- les systèmes Windows 7 ;

- les systèmes Windows Server 2012 ;

- et les systèmes Windows 8.

Variable

Description

Compatibilité

AllUsersProfile

Renvoie le chemin d'accès complet jusqu'au répertoire des données utilisateurs communes à tous les utilisateurs. Par défaut : %HomeDrive%\ProgramData.

Disponible sur tous les systèmes NT.

AppData

Renvoie le répertoire commun des données d'application sous la forme
%UserProfile%\AppData\Roaming.

Disponible sur tous les systèmes NT.

CD

Renvoie le chemin d'accès complet jusqu'au répertoire dans lequel le script est en train de s'exécuter, ce répertoire peut être différent du répertoire dans lequel le script s'est lancé (si celui-ci a été « relocalisé » au cours de son exécution).

Disponible sur tous les systèmes NT.

CmdCmdLine

Renvoie la ligne de commande originale qui a appelé l'interpréteur de commande.

Disponible sur tous les systèmes NT.

CmdExtVersion

Renvoie le numéro de version des extensions de commande du processeur de commande en cours.

Disponible sur tous les systèmes NT.

CommonProgramFiles

Renvoie le chemin d'accès complet jusqu'au répertoire des fichiers communs aux applications 32bits sur les systèmes 32bits ou aux applications 64bits sur les systèmes 64bits, soit : %ProgramFiles%\Common Files.

Disponible sur tous les systèmes NT.

CommonProgramFiles(x86)

Renvoie le chemin d'accès complet jusqu'au répertoire des fichiers communs aux applications 32bits sur les systèmes 64bits :
%ProgramFiles(x86)%\Common Files.

Disponible uniquement sur les systèmes NT 64bits.

CommonProgramW6432

Renvoie le chemin d'accès complet jusqu'au répertoire des fichiers communs aux applications 16 bits sur les systèmes 64bits et sur les systèmes 32bits Vista et supérieur : %ProgramW6432%\Common Files.

Disponible uniquement sur les systèmes NT 64bits et sur les systèmes 32bits Vista et supérieur.

ComputerName

Renvoie le nom de l'ordinateur sur lequel le script est en train de s'exécuter.

Disponible sur tous les systèmes NT.

ComSpec

Renvoie le chemin d'accès complet vers l'interpréteur : %WinDir%\system32\cmd.exe.

Disponible sur tous les systèmes NT.

Date

Renvoie la date actuelle en utilisant le même format que la commande date.

Disponible sur tous les systèmes NT.

ErrorLevel

Renvoie la valeur du code d'erreur en cours. Cette valeur est modifiée après chaque ligne de commande, en fonction du résultat de la commande. En général, la variable ErrorLevel renvoie 1 ou plus en cas d'erreur de la dernière commande et 0 si aucune erreur ne s'est produite. Cependant, il arrive que ce comportement varie selon les commandes, il est donc recommandé de se reporter à l'aide concernant ladite commande.

Disponible sur tous les systèmes NT.

FP_NO_HOST_CHECK

N.C.

Disponible sur tous les systèmes NT.

HighestNumaNodeNumber

Renvoie le numéro de nœud NUMA le plus élevé de l'ordinateur sur lequel s'exécute le script.

Disponible uniquement sur les systèmes NT 64bits.

HomeDrive

Renvoie le point de montage de la partition qui accueille les répertoires utilisateurs. Par défaut : C:.

Disponible sur tous les systèmes NT.

HomePath

Renvoie le chemin d'accès vers le répertoire de l'utilisateur actuellement logué. Par défaut : \Users\%UserName%.

Disponible sur tous les systèmes NT.

LocalAppData

Renvoie le répertoire local des données d'application sous la forme : %UserProfile%\AppData\Local.

Disponible sur tous les systèmes NT.

LogonServer

Renvoie l'URL locale du système d'exploitation sous la forme \\%ComputerName%.

Disponible sur tous les systèmes NT.

Number_Of_Processors

Renvoie le nombre de cœurs logiques de l'ordinateur sur lequel le script est en train de s'exécuter.

Disponible sur tous les systèmes NT.

OS

Renvoie le type de noyau sur lequel repose le système d'exploitation, sur les systèmes NT cette variable renverra toujours la chaîne Windows_NT.

Disponible sur tous les systèmes NT.

Path

Renvoie la liste des répertoires reconnus par le système comme contenant des exécutables, chaque répertoire est listé par son chemin d'accès complet suffixé par un point-virgule. Si un exécutable se trouve dans un des répertoires de cette liste, il n'est pas nécessaire de fournir un chemin d'accès complet pour l'appeler en ligne de commande.

Disponible sur tous les systèmes NT.

PathExt

Renvoie la liste des extensions de fichier reconnues par le système comme étant des extensions de fichiers exécutables. Si une extension de fichier ne figure pas dans cette liste, alors le fichier ne peut être appelé en tant que commande.

Disponible sur tous les systèmes NT.

Processor_Architecture

Renvoie le type d'architecture (32/64bits) du processeur sur lequel s'exécute le script. Les valeurs possibles sont : X86 pour les processeurs 32bits, AMD64 pour les processeurs 64bits basés sur l'architecture x86 et IA64 pour les processeurs Itanium.

Disponible sur tous les systèmes NT.

Processor_Identifier

Renvoie une identification précise du processeur sur lequel s'exécute le script. Cette identification est une chaîne de caractères composée du type d'architecture suivi de la famille, du modèle, de la révision et enfin du fabricant du processeur.

Disponible sur tous les systèmes NT.

Processor_Level

Renvoie une identification précise de la famille de la micro-architecture du processeur.

Disponible sur tous les systèmes NT.

Processor_Revision

Renvoie une identification précise du modèle et de la révision du processeur sous forme d'une chaîne de caractères représentant un nombre hexadécimal sur deux octets, le premier correspond au modèle et le second à la révision.

Disponible sur tous les systèmes NT.

ProgramData

Renvoie le répertoire commun des données d'application 64bits sous la forme : %SystemDrive%\ProgramData.

Disponible uniquement sur les systèmes NT 64bits.

ProgramFiles

Renvoie le chemin d'accès complet vers le répertoire « ProgramFiles ». Ce répertoire contient les applications 32bits sur les systèmes 32bits ou les applications 64bits sur les systèmes 64bits.

Disponible sur tous les systèmes NT.

ProgramFiles(x86)

Renvoie le chemin d'accès complet vers le répertoire « ProgramFiles(x86) ». Ce répertoire contient les applications 32bits sur les systèmes 64bits.

Disponible uniquement sur les systèmes NT 64bits.

ProgramW6432

Renvoie le chemin d'accès complet vers le répertoire « ProgramW6432 ». Ce répertoire contient les applications 16bits sur les systèmes 64bits et sur les systèmes 32bits Vista et supérieur.

Disponible sur les systèmes NT 32 et 64bits Vista et supérieur.

Prompt

Renvoie la chaîne configurée pour l'affichage standard, par défaut : $P$G. Voir l'aide de la commande prompt pour plus d'information : prompt /?.

Disponible sur tous les systèmes NT.

PSModulePath

Renvoie le chemin d'accès complet vers les modules PowerShell.

Disponible uniquement sur les systèmes Vista et supérieur. (XP avec mise à jour KB926140)

Public

Renvoie le chemin d'accès complet vers le répertoire des documents publics sous la forme %HomeDrive%\Users\Public.

Disponible uniquement sur les systèmes Vista et supérieur.

Random

Renvoie un nombre aléatoire compris entre 0 et 32767.

Disponible sur tous les systèmes NT.

SessionName

Renvoie le nom de la session en cours. Par défaut : Console.

Disponible sur tous les systèmes NT.

SystemDrive

Renvoie le point de montage de la partition sur laquelle est installé le système d'exploitation.

Disponible sur tous les systèmes NT.

SystemRoot

Renvoie le chemin d'accès complet vers le système d'exploitation sous la forme
%SystemDrive%\Windows.

Disponible sur tous les systèmes NT.

Temp

Renvoie le chemin d'accès complet jusqu'au répertoire des fichiers temporaires de l'utilisateur. Par défaut : %UserProfile%\AppData\Local\Temp.

Disponible sur tous les systèmes NT.

Time

Renvoie l'heure en cours en utilisant le même format que la commande time.

Disponible sur tous les systèmes NT.

Tmp

Identique à Temp.

Disponible sur tous les systèmes NT.

UserDomain

Renvoie le nom de domaine de l'ordinateur sur lequel s'exécute le script. Si le système n'appartient pas à un domaine alors le nom de domaine sera le nom de l'ordinateur sur lequel s'exécute le script.

Disponible sur tous les systèmes NT.

UserName

Renvoie le nom de l'utilisateur actuellement logué.

Disponible sur tous les systèmes NT.

UserProfile

Renvoie le chemin d'accès complet vers le répertoire utilisateur de l'utilisateur actuellement logué sous la forme : %HomeDrive%\Users\%UserName%.

Disponible sur tous les systèmes NT.

WinDir

Renvoie le chemin d'accès complet jusqu'au répertoire d'installation du système. Par défaut : %SystemDrive%\Windows.

Disponible sur tous les systèmes NT.

Tableau 3 : les variables d'environnement

II-B - La commande « set »

La commande set gère les variables dans l'interpréteur, elle permet :

- de créer une variable ;

- d'attribuer une valeur à une variable ;

- de modifier le contenu d'une variable ;

- de supprimer le contenu d'une variable ;

- d'effectuer des opérations mathématiques ou logiques entre des nombres ;

- de récupérer la saisie d'un utilisateur pour la placer dans une variable ;

- et de transtyper le contenu d'une variable.

Voici la syntaxe de la commande set.

Attribue à la variable une valeur sous forme de chaîne de caractères (où <variable> est son identifiant et <chaîne> est une chaîne de caractères).

set ["]<variable>=<chaîne>["]

Attribue à la variable une valeur sous forme d'un nombre entier signé (où <expression> est une expression numérique à évaluer : détaillé par la suite).

set /a ["]<expression>["]

Attribue à la variable une valeur, saisie par l'utilisateur, sous forme de chaîne de caractères après l'affichage de la chaîne d'invite si elle est spécifiée (où <variable> est son identifiant et où <chaîne_invite> est une chaîne de caractères affichée à l'utilisateur afin de l'inviter à saisir une chaîne au clavier).

set /p ["]<variable>=[<chaîne_invite>]["]

Supprime la valeur de la variable de la mémoire, son identifiant reste indexé par l'interpréteur, mais sa valeur est indéfinie.

set ["]<variable>=["]

Voici les règles usuelles de la commande set :

- Si la commande set est employée sans paramètre, elle affiche les variables définies dans le contexte courant (détaillé par la suite).

- Si elle est utilisée avec comme paramètre une chaîne (ou un nom de variable), sans valeur ni signe égal ; alors elle affiche la variable dont le nom correspond à la chaîne donnée en paramètre et/ou les variables ayant un nom commençant par la chaîne donnée en paramètre.

- Si elle est utilisée avec un nom de variable et un signe égal sans valeur alors le contenu de la variable est vidé de la mémoire, il est possible de tester si une variable est définie, mais nous aborderons ce point au prochain chapitre.

- Toute chaîne non numérique dans l'expression à évaluer est traitée comme un identifiant de variable et est convertie en nombre avant d'être utilisée (dans son utilisation avec le paramètre /a), si la variable n'existe pas ou qu'elle est indéfinie, elle prend comme valeur 0.

- Une expression numérique à évaluer devrait toujours être placée entre guillemets afin de permettre l'utilisation des opérateurs logiques et des opérateurs de groupement.

Voyons plus en détail le fonctionnement de la commande set avec le script 9.

Script 9

1. @echo off

2. set VAR_Espace_Un="Ma chaîne avec des espaces"

3. set VAR_Espace_Deux"=Ma chaîne avec des espaces"

4. set "VAR_Espace_Trois=Ma chaîne avec des espaces"

5. ::il y a quatre espaces à la fin de la ligne suivante

6. set /p VAR_Saisie_Un=    ma saisie un:

7. set /p "VAR_Saisie_Deux=    ma saisie deux:    "

8. set /a VAR_Calcul_Un=1+10

9. set /a VAR_Calcul_Deux"=2+20"

10. set /a "VAR_Calcul_Trois=3+30"

11. echo.

12. echo %VAR_Espace_Un%

13. echo %VAR_Espace_Deux%

14. echo %VAR_Espace_Trois%

15. echo.

16. set VAR_Saisie

17. echo.

18. set VAR_Calcul

19. echo.

20. pause

Comme vous pouvez le constater, les guillemets occupent une place prépondérante dans ce script ; observez bien où ils sont placés. De la ligne 2 à la ligne 4, les valeurs des variables sont des chaînes de caractères avec des espaces. La déclaration de la variable VAR_Espace_Un se fait avec des guillemets placés de part et d'autre de la chaîne, la déclaration de la variable VAR_Espace_Deux se fait avec des guillemets : un placé avant le signe égal et l'autre à la fin de la chaîne ; la déclaration de la troisième variable VAR_Espace_Trois se fait avec des guillemets : un placé avant le nom de la variable et l'autre à la fin de la ligne. Si on se reporte à l'affichage obtenu, on remarque que la première et la troisième variables affichent une sortie correcte ; la deuxième variable quant à elle ne fonctionne pas ; on déduit donc facilement que si les guillemets s'ouvrent avant l'identifiant de la variable et se ferment après la valeur de la variable, ils ne font pas partie de la variable ; et si les guillemets s'ouvrent avant la valeur de la variable et se ferment après la valeur de la variable, ils font partie de la variable.

Les deux saisies de l'utilisateur donnent aussi un résultat intéressant, la ligne de commande qui déclare VAR_Saisie_Un n'utilise pas de guillemets ; par contre la ligne de commande qui déclare VAR_Saisie_Deux, elle, en utilise. Lors de l'affichage des invites de saisie utilisateur, les espaces se trouvant avant les chaînes d'invites ont disparu, et ceux se trouvant après sont affichés. On en déduit que les espaces en début de ligne sont ignorés et que ceux de la fin sont considérés comme faisant partie de la chaîne d'invite.

En ce qui concerne les variables VAR_Calcul_XXX, elles donnent toutes un résultat valide cependant il est toujours préférable de respecter les règles de syntaxe ; c'est-à-dire toujours mettre l'expression numérique entre guillemets. Cela sera utile dans la suite du chapitre.

II-C - Les variables de type chaîne de caractères

Ce sont, à mon sens, les plus utilisées en batch. Ce sont toutes les variables composées de caractères alphanumériques et autres caractères. Les chaînes de caractères sont déclarées via la commande set et il n'est pas nécessaire d'utiliser des guillemets pour que les espaces soient pris en compte comme faisant partie de la chaîne, et ce, où qu'ils soient placés dans la chaîne.

Elles offrent aussi d'autres possibilités comme la substitution de chaîne dans l'expansion de la variable. Ainsi avec la syntaxe suivante, il est possible de modifier la variable lors de son expansion sans que cela n'altère sa valeur (où <variable> est l'identifiant de la variable, <old_string> est la chaîne à remplacer et <new_string> est la chaîne à insérer à la place de <old_string>). Exemple avec le script 10.

%<variable>:<old_string>=<new_string>%

Script 10

1. @echo off

2. set "X=une chaîne"

3. echo La variable avec substitution: %X:une=ma%

4. echo La valeur réelle de la variable: %X%

5. echo.

6. pause

<old_string> peut aussi être une chaîne précédée d'un astérisque (*), dans ce cas, la substitution commencera du début de la valeur de la variable et se finira après la première occurrence de la chaîne spécifiée dans la substitution. Exemple avec le script 11.

Script 11

1. @echo off

2. set "X=string"

3. echo Substitution avec astérisque: %X:*t=d%

4. echo.

5. pause

Il ne faut jamais utiliser un astérisque seul dans la substitution, sans quoi, l'interpréteur de commande se fermera sans autres formes de procès. L'astérisque en fin de chaîne ne provoque pas d'erreur, mais ne permet pas la substitution, la variable est expansée avec sa valeur d'origine.

La substitution ne modifie pas la valeur de la variable, elle transforme juste la variable au moment de son expansion. Pour modifier la valeur de la variable, il faut le faire avec la commande set. Exemple avec le script 12.

Script 12

1. @echo off

2. set "VAR=ma chaîne"

3. echo %VAR%

4. set "VAR=%VAR:chaîne=voiture%"

5. echo %VAR%

6. pause

Vous pouvez aussi ne développer qu'une partie d'une chaîne en sélectionnant les caractères voulus. Cela se fait via la syntaxe suivante (où <variable> est l'identifiant de la variable, <index> est l'index du premier caractère en partant de 0 et <longueur> est la longueur de la sélection). Exemple avec le script 13.

%<variable>:~<index>,<longueur>%

Script 13

1. @echo off

2. set "VAR=ma chaîne"

3. echo %VAR:~0,2%

4. set "VAR=%VAR:~3,6%"

5. echo %VAR%

6. pause

Si la <longueur> n'est pas spécifiée, tout le reste de la chaîne est sélectionné, en partant du caractère fourni en <index>. Si une des valeurs données est négative, alors la valeur utilisée est la longueur totale de la chaîne ajoutée à la valeur négative.

Script 14

1. @echo off

2. rem Dans ce script, les caractères ^^ servent à échapper les caractères spéciaux.

3. set "VAR=ma chaîne"

4. rem longueur de var :  9 caractères

5. echo %VAR:~-6%

6. rem   9+^(-6^) = 9-6 = 3

7. rem la chaîne sera développée à partir de l'index 3 et jusqu'à la fin ^(aucune longueur n'est donnée^).

8. echo %VAR:~0,-7%

9. rem    9+^(-7^) = 9-7 = 2

10. rem la chaîne sera développée à partir de l'index 0 et sur une longueur de 2 caractères

11. pause

II-D - Les variables de type nombre entier signé

La commande set /a permet d'utiliser les nombres entiers signés allant de -2 147 483 648 à +2 147 483 647 (codés sur 32bits). Si vous n'utilisez pas le paramètre /a dans la commande, alors la séquence de chiffres sera considérée comme étant une chaîne de caractères, et non un nombre. La syntaxe de la commande set /a est la suivante.

set /a <expression>

<expression> utilise, quant à elle, la syntaxe suivante.

["]<identifiant>[<attribution>[{<immédiate>|<sous-expression>}]]["]

Les expressions numériques doivent être placées entre des guillemets si elles contiennent des opérateurs logiques ou de groupement (détaillé plus loin dans ce chapitre). Ces différentes constituantes sont :

- <identifiant> qui est l'identifiant de la variable de destination ;

- <attribution> qui est l'opérateur d'attribution de l'expression, comme le signe égal qui effectue l'opération d'attribuer une valeur à une variable, d'autres opérateurs d'attribution seront détaillés dans cette section ;

- puis soit <immédiate> qui est une valeur immédiate (un nombre quelconque), soit <sous-expression> qui est une sous-expression, ces dernières seront détaillées dans le reste de cette section.

Vous pouvez spécifier plusieurs expressions en les séparant par des virgules.

Script 15

@echo off
set /a "VAR1=1", "VAR2=2"
echo VAR1: %VAR1%
echo VAR2: %VAR2%
pause

Les chaînes de caractères présentes dans l'expression sont considérées comme des variables et sont expansées (à l'exception de l'identifiant de la variable de destination), cela permet de faire des opérations sur des variables sans avoir à utiliser de symbole % pour leur expansion.

Script 16

@echo off
set /a "VAR1=1"
set /a "VAR2=VAR1"
echo VAR1: %VAR1%
echo VAR2: %VAR2%
pause

Si un nom de variable inexistante ou indéfinie est utilisé, alors elle prend la valeur 0.

Script 17

1. @echo off

2. rem La variable VAR1 n'existe pas.

3. set /a "VAR2=VAR1"

4. echo VAR2: %VAR2%

5. pause

Les sous-expressions sont constituées de nombres, d'opérateurs et éventuellement de parenthèses, ces dernières s'utilisent de la même manière qu'en mathématiques et elles n'ont pas besoin d'être échappées si, et seulement si, les guillemets sont utilisés. Toutes les constituantes d'une sous-expression sont soumises à la préséance, c'est-à-dire l'ordre dans lequel les diverses constituantes vont être évaluées. L'ordre de préséance utilisé est le suivant (dans l'ordre décroissant d'évaluation) :

- le groupement soit ( ) ;

- les opérateurs unaires ! ~ - ;

- les opérateurs arithmétiques * / % ;

- les opérateurs arithmétiques + - ;

- le décalage logique << >> ;

- le ET au niveau du bit & ;

- le OU exclusif au niveau du bit ^ ;

- le OU inclusif au niveau du bit | ;

- l''attribution = *= /= %= += -= &= ^= |= <<= >>= ;

- le séparateur d'expression ,.

Il faut également noter que l'utilisation du nombre -2 147 483 648 tel quel dans une expression provoque une erreur, c'est dû au transtypage effectué par l'interpréteur. Ce dernier évalue d'abord la chaîne 2 147 483 648 afin de la transtyper puis lui applique l'opérateur unaire -. Mais comme le nombre 2 147 483 648 va au-delà de la définition d'un nombre sur 32bits, l'opération génère une erreur, exemple avec le script suivant.

Script 18

1. @echo off

2. echo Nombre brute:

3. set /a "ParseError=-2147483648"

4. echo %ParseError%

5. echo.

6. echo Nombre avec évaluation:

7. set /a "ParseError=-2147483647 - 1"

8. echo %ParseError%

9. echo.

10. pause

Une autre méthode qui fonctionne pour ce cas est la suivante.

Script 19

1. @echo off

2. set "SInt32=-2147483648"

3. set /a "SInt32"

4. echo %SInt32%

5. pause

II-D-1 - Les opérations arithmétiques

La commande set /a prend en charge les cinq opérations arithmétiques suivantes :

- l'addition ;

- la soustraction ;

- la multiplication ;

- la division ;

- et le modulo (le modulo est une opération qui sert à récupérer le reste d'une division).

Opérateur arithmétique

Opérateur d'attribution

Opération effectuée

+

+=

Addition

-

-=

Soustraction

*

*=

Multiplication

/

/=

Division

%

%=

Modulo

Tableau 4 : Opérations arithmétiques

Une note particulière pour le modulo : dans un script de commande, le symbole du modulo % doit être préfixé du caractère d'échappement %. Dans ce cas, c'est un piège dans lequel le novice peut se perdre, le premier symbole % est un caractère d'échappement qui permet au deuxième symbole %, le modulo, d'être pris en compte à l'exécution du script. Dans l'interpréteur de commande, le modulo (%) n'as pas besoin d'être échappé, c'est une spécificité du script. Ainsi la commande suivante fonctionne dans l'interpréteur.

set /a "Mod=5 % 2"

Alors que celle-ci ne fonctionne pas.

set /a "Mod=5 %% 2"

Dans un script, par contre, c'est l'inverse.

Script 20

1. @echo off

2. set /a "Mod=5 % 2"

3. echo Mod1: %Mod%

4. set /a "Mod=5 %% 2"

5. echo Mod2: %Mod%

6. pause

Ainsi, les opérations arithmétiques s'utilisent de manière « classique » dans les sous-expressions. Si une variable inexistante ou indéfinie est utilisée dans une sous-expression, elle prend la valeur 0, exemple avec le script 21.

Script 21

1. @echo off

2. set /a "Un=1"

3. set /a "Deux=1 + 1"

4. set /a "Trois=4 - 1"

5. set /a "Quatre=2 * 2"

6. set /a "Cinq=10 / 2"

7. set /a "Six=26 %% 10"

8. set /a "Sept=(( 2 + 1 ) * 2 ) + 1"

9. rem La variable Dix n'existe pas.

10. set /a "Huit=8+Dix","Neuf=10-Un"

11. echo %Un%, %Deux%, %Trois%, %Quatre%, %Cinq%, %Six%, %Sept%, %Huit%, %Neuf%

12. echo.

13. pause

L'attribution permet d'effectuer des opérations sur des variables existantes, si une variable inexistante ou indéfinie est utilisée, elle prend la valeur 0.

Script 22

1. @echo off

2. set /a "Attrib1=1","Attrib2=2","Attrib3=3","Attrib4=4","Attrib5=5"

3. rem La variable Attrib0 n'existe pas.

4. set /a "Attrib0+=1"

5. set /a "Attrib1+=1"

6. set /a "Attrib2*=2"

7. set /a "Attrib3-=2"

8. set /a "Attrib4/=2"

9. set /a "Attrib5%%=2"

10. echo %Attrib0%, %Attrib1%, %Attrib2%, %Attrib3%, %Attrib4%, %Attrib5%

11. echo.

12. pause

II-D-2 - Les opérations logiques

Elles ne peuvent s'effectuer que sur des nombres et suivent les règles de l'algèbre de Boole. Les opérations logiques prises en charge par la commande set /a possèdent, elles aussi, des opérateurs d'attribution. Exemple avec le script 23.

Opérateur logique

Opérateur d'attribution

Description

&

&=

ET logique au niveau du bit. (Bitwise AND)

^

^=

OU exclusif au niveau du bit. (Bitwise XOR)

|

|=

OU inclusif au niveau du bit. (Bitwise OR)

<<

<<=

Décalage logique vers la gauche. Le bit entrant est à zéro (pour l'élévation par puissances de deux).

>>

>>=

Décalage logique vers la droite. Le bit entrant est égal au bit de signe (pour la propagation du bit de signe).

Tableau 5 : Les opérateurs logiques

Script 23

@echo off
set /a "Val1=-2147483647 - 1"
set /a "Val2=1"
set /a "Val1SwitchL01=Val1<<1","Val1SwitchL15=Val1<<15","Val1SwitchL31=Val1<<31"
set /a "Val2SwitchL01=Val2<<1","Val2SwitchL15=Val2<<15","Val2SwitchL31=Val2<<31"
set /a "Val1SwitchR01=Val1>>1","Val1SwitchR15=Val1>>15","Val1SwitchR31=Val1>>31"
set /a "Val2SwitchR01=Val2>>1","Val2SwitchR15=Val2>>15","Val2SwitchR31=Val2>>31"
set /a "Val1AttribSwitchR=Val1","Val1AttribSwitchR>>=15"
set /a "Val2AttribSwitchL=Val2","Val2AttribSwitchL<<=15"
echo %Val1% ^<^< 1: %Val1SwitchL01%
echo %Val1% ^<^< 15: %Val1SwitchL15%
echo %Val1% ^<^< 31: %Val1SwitchL31%
echo.
echo %Val2% ^<^< 1: %Val2SwitchL01%
echo %Val2% ^<^< 15: %Val2SwitchL15%
echo %Val2% ^<^< 31: %Val2SwitchL31%
echo.
echo %Val1% ^>^> 1: %Val1SwitchR01%
echo %Val1% ^>^> 15: %Val1SwitchR15%
echo %Val1% ^>^> 31: %Val1SwitchR31%
echo.
echo %Val2% ^>^> 1: %Val2SwitchR01%
echo %Val2% ^>^> 15: %Val2SwitchR15%
echo %Val2% ^>^> 31: %Val2SwitchR31%
echo.
echo %Val1% ^>^>= 15: %Val1AttribSwitchR%
echo %Val2% ^<^<= 15: %Val2AttribSwitchL%
echo.
set /a "Val3=1431655765"
set /a "Val4=-858993460"
set /a "Val3LogicalOrVal4=Val3 | Val4"
set /a "Val3LogicalXorVal4=Val3 ^ Val4"
set /a "Val3LogicalAndVal4=Val3 & Val4"
set /a "Val3AttribOrVal4=Val3","Val3AttribOrVal4|=Val4"
set /a "Val3AttribXorVal4=Val3","Val3AttribXorVal4^=Val4"
set /a "Val3AttribAndVal4=Val3","Val3AttribAndVal4&=Val4"
echo %Val3% ^| %Val4%: %Val3LogicalOrVal4%
echo %Val3% ^|= %Val4%: %Val3AttribOrVal4%
echo.
echo %Val3% ^^ %Val4%: %Val3LogicalXorVal4%
echo %Val3% ^^= %Val4%: %Val3AttribXorVal4%
echo.
echo %Val3% ^& %Val4%: %Val3LogicalAndVal4%
echo %Val3% ^&= %Val4%: %Val3AttribAndVal4%
echo.
pause

II-D-3 - Les opérateurs unaires

N.B. : Dans les sections II.D.3, II.E et II.F de ce document, sont abordés différents concepts de représentation numérique communément utilisés en informatique, tels que :

- l'écriture de nombre en binaire ;

- l'écriture de nombre en hexadécimal ;

- l'écriture de nombre en octal ;

- la représentation des nombres en complément à un ;

- la représentation des nombres entiers signés en complément à deux.

Il est donc nécessaire, si vous ne les connaissez pas, de faire des recherches sur Wikipédia.

- Système binaire, complément à un et complément à deux ;

- Notation octale ;

- Notation hexadécimale.

Les opérateurs unaires ne s'appliquent qu'aux nombres qu'ils précèdent, ils ne possèdent donc pas d'opérateur d'attribution. Leur syntaxe est la suivante (où <unaire> est l'opérateur unaire et <nombre> est le nombre auquel il s'applique).

<unaire><nombre>

Opérateur unaire

Description

!

NON logique, renvoie 1 si le nombre est égal à 0 et 0 sinon. (Logical NOT).

~

NON au niveau du bit, complément à un. (Bitwise NOT)

-

Renvoie la valeur inverse, soit un nombre négatif si le nombre d'origine est positif, complément à deux. (NEG)

Tableau 6 : Les opérateurs unaires

Exemple avec le script 24.

Script 24

1. @echo off

3. rem Veuillez vous assurer d'utiliser le bon encodage, sans quoi l'affichage ne sera pas celui attendu.

5. set /a "Val1=2147483647"

6. set /a "Val2=1"

7. set /a "Val3=0"

9. set /a "LogicalNOT_Val1=!Val1"

10. set /a "LogicalNOT_Val2=!Val2"

11. set /a "LogicalNOT_Val3=!Val3"

13. set /a "BitwiseNOT_Val1=~Val1"

14. set /a "BitwiseNOT_Val2=~Val2"

15. set /a "BitwiseNOT_Val3=~Val3"

17. set /a "NEG_Val1=-Val1"

18. set /a "NEG_Val2=-Val2"

19. set /a "NEG_Val3=-Val3"

21. echo              ╔════════════════════════════════════════╗

22. echo              ║                 Nombre                 ║

23. echo ╔════════════╬═════════════╦═════════════╦════════════╣

24. echo ║ Opérateur  ║ %Val1%  ║      %Val2%      ║     %Val3%      ║

25. echo ╠════════════╬═════════════╬═════════════╬════════════╣

26. echo ║ LogicalNOT ║ %LogicalNOT_Val1%           ║ %LogicalNOT_Val2%           ║ %LogicalNOT_Val3%          ║

27. echo ╠════════════╬═════════════╬═════════════╬════════════╣

28. echo ║ BitwiseNOT ║ %BitwiseNOT_Val1% ║ %BitwiseNOT_Val2%          ║ %BitwiseNOT_Val3%         ║

29. echo ╠════════════╬═════════════╬═════════════╬════════════╣

30. echo ║ NEG        ║ %NEG_Val1% ║ %NEG_Val2%          ║ %NEG_Val3%          ║

31. echo ╚════════════╩═════════════╩═════════════╩════════════╝

33. echo.

34. pause

II-E - Les nombres entiers signés en notation hexadécimale

Les nombres en notation hexadécimale doivent être déclarés comme des nombres entiers en notation décimale (ce que nous avons utilisé jusqu'à maintenant) avec la commande set /a "<expression>" et toujours être préfixés par 0x (chiffre zéro suivi de la lettre « x »). La commande set /a a pour effet de transformer toutes les valeurs entrées en valeurs numériques. Si c'est une chaîne de caractères alors la commande cherchera une variable portant ce nom, si c'est un nombre hexadécimal alors la commande set /a codera le nombre tel quel en binaire. Cependant, même si un nombre est fourni en notation hexadécimale, l'interpréteur l'expanse toujours en notation décimale, exemple avec le script 25.

Script 25

1. @echo off

2. rem le nombre 0x1 vaut 1 et le nombre 0x3 vaut 3.

3. set /a "Hexa=0x1 + 0x3"

4. echo Résultat:    %Hexa%

5. pause

Le résultat du script 25 nous montre que le calcul s'effectue correctement, de même avec le script 26.

Script 26

1. @echo off

2. rem le nombre 0x5 vaut 5 et le nombre 0x8 vaut 8.

3. set /a "Hexa=0x5 + 0x8"

4. rem Le résultat doit donc être 0xD qui vaut 13.

5. echo Résultat:    %Hexa%

6. pause

Jusque-là tout va bien même si le résultat n'est pas en notation hexadécimale, il est quand même celui attendu. Le problème c'est que l'interpréteur de commande utilise toujours la représentation en complément à deux pour coder un nombre entier signé. Ainsi, dès lors que l'on utilise un nombre en notation hexadécimale supérieur à 0x7FFFFFFF (soit 2 147 483 647 en notation décimale), ce nombre est en fait un nombre négatif comme le montre le script 27.

Script 27

1. @echo off

2. set /a "Hexa=0x80000000"

3. echo Résultat:    %Hexa%

4. pause

Un résultat plutôt déroutant pour quiconque ne s'y attend pas, en effet la représentation en complément à deux code les nombres négatifs de 0xFFFFFFFF, soit -1, à 0x80000000, soit -2 147 483 648. Exemple avec le script 28.

Script 28

1. @echo off

2. rem 0x80000000 vaut -2147483648 et 0x7FFFFFFF vaut 2147483647

3. set /a "Hexa=0x80000000 + 0x7FFFFFFF"

4. rem Le résultat doit donc être 0xFFFFFFFF qui vaut -1.

5. echo Résultat:    %Hexa%

6. pause

Comme vous pouvez le voir, on additionne 0x80000000 (soit -2 147 483 648 en représentation hexadécimale en complément à deux) à 0x7FFFFFFF (soit +2 147 483 647, toujours dans la même représentation) ce qui nous donne le résultat de -1, le résultat est un nombre entier signé décimal utilisant la représentation en complément à deux codé sur 32bits soit 0xFFFFFFFF. Pour récupérer un nombre hexadécimal, il faut le calculer à l'aide d'un algorithme de divisions successives ; et pour les nombres négatifs, de l'opérateur unaire « Bitwise NOT » dont dispose la commande set /a, voir le script 29.

Script 29

1. @echo off

2. rem On récupère un nombre sous forme de chaîne et le place dans SInt32.

3. set "SInt32=-2147483648"

4. rem On transtype SInt32 en nombre pour qu'il soit évalué correctement.

5. set /a "SInt32"

6. rem Puis on effectue un NON au niveau du bit pour obtenir son complément à un ^(nombre négatif^).

7. set /a "SInt32=~SInt32"

8. rem On utilise un algorithme de divisions successives par 16 pour obtenir des

9. rem nombres en base 16 ^(hexadécimale^), chaque nombre obtenu forme un quartet.

10. set /a "Nibble0= SInt32 %% 16","HighOrder0= SInt32 / 16"

11. set /a "Nibble1=HighOrder0 %% 16","HighOrder1=HighOrder0 / 16"

12. set /a "Nibble2=HighOrder1 %% 16","HighOrder2=HighOrder1 / 16"

13. set /a "Nibble3=HighOrder2 %% 16","HighOrder3=HighOrder2 / 16"

14. set /a "Nibble4=HighOrder3 %% 16","HighOrder4=HighOrder3 / 16"

15. set /a "Nibble5=HighOrder4 %% 16","HighOrder5=HighOrder4 / 16"

16. set /a "Nibble6=HighOrder5 %% 16"

17. set /a "Nibble7=HighOrder5 / 16"

18. rem On retranche chaque valeur à 15 pour récupérer le nombre original.

19. set /a "Nibble0=15 - Nibble0"

20. set /a "Nibble1=15 - Nibble1"

21. set /a "Nibble2=15 - Nibble2"

22. set /a "Nibble3=15 - Nibble3"

23. set /a "Nibble4=15 - Nibble4"

24. set /a "Nibble5=15 - Nibble5"

25. set /a "Nibble6=15 - Nibble6"

26. set /a "Nibble7=15 - Nibble7"

27. rem Les quartets 0 à 3 forment le Least Significant Word ^(LSW^) ou mot de poids faible dans la langue de Molière.

28. set "LSW=%Nibble3% %Nibble2% %Nibble1% %Nibble0%"

29. rem Les quartets 4 à 7 forment le Most Significant Word ^(MSW^) ou mot de poids fort toujours dans la langue de Molière.

30. set "MSW=%Nibble7% %Nibble6% %Nibble5% %Nibble4%"

31. echo %MSW% %LSW%

32. rem On récupère un nouveau nombre ^(nombre positif^).

33. set "SInt32=2147483647"

34. set /a "SInt32"

35. set /a "Nibble0= SInt32 %% 16","HighOrder0= SInt32 / 16"

36. set /a "Nibble1=HighOrder0 %% 16","HighOrder1=HighOrder0 / 16"

37. set /a "Nibble2=HighOrder1 %% 16","HighOrder2=HighOrder1 / 16"

38. set /a "Nibble3=HighOrder2 %% 16","HighOrder3=HighOrder2 / 16"

39. set /a "Nibble4=HighOrder3 %% 16","HighOrder4=HighOrder3 / 16"

40. set /a "Nibble5=HighOrder4 %% 16","HighOrder5=HighOrder4 / 16"

41. set /a "Nibble6=HighOrder5 %% 16"

42. set /a "Nibble7=HighOrder5 / 16"

43. set "LSW=%Nibble3% %Nibble2% %Nibble1% %Nibble0%"

44. set "MSW=%Nibble7% %Nibble6% %Nibble5% %Nibble4%"

45. echo %MSW% %LSW%

46. pause

La sortie du script 29 nous donne bien des nombres hexadécimaux, mais dans une représentation décimale (8 0 0 0 0 0 0 0 soit -2 147 483 648 codé 0x80000000 et 7 15 15 15 15 15 15 15 soit 2 147 483 647 codé 0x7FFFFFFF), pour pouvoir les convertir dans une notation hexadécimale ; il nous faut utiliser les conditions que nous étudierons au prochain chapitre.

II-F - Les nombres entiers signés en notation octale

Les nombres en notation octale fonctionnent comme les autres et supportent les mêmes opérations. Pour être considérés par l'interpréteur comme des nombres en notation octale, ils doivent être préfixés par 0 (chiffre zéro). Ils vont donc de -2 147 483 648 à 2 147 483 647 (codés sur 32bits) soit en notation octale -020000000000 à +017777777777. Les nombres en notation octale sont codés tels quels en binaire, mais le principe selon lequel tout nombre est expansé en entier signé en notation décimale codé en complément à deux s'applique aussi pour eux. Notez aussi qu'ils ont le même problème de transtypage que les nombres décimaux, exemple avec le script 30.

Script 30

@echo off
set /a "Octal1=-017777777777 - 1"
set /a "Octal2=017777777777"
set /a "Octal3=Octal1 + Octal2"
echo Octal1: %Octal1%
echo Octal2: %Octal2%
echo Octal3: %Octal3%
pause

Notez aussi que l'utilisation des nombres « 08 » et « 09 » génère toujours une erreur du fait qu'ils sont préfixés par « 0 » ; et, que « 8 » et « 9 » ne sont pas des chiffres octaux. Lorsque la commande set /a rencontre un « 0 » en début de nombre, elle considère que tous les chiffres qui se trouvent après sont des octaux et lorsqu'elle rencontre « 8 » ou « 9 », le transtypage ne peut s'effectuer et la commande se termine sur une erreur. Exemple avec le script 31.

Script 31

@echo off
echo Nombre 08:
set /a "Octal1=08"
echo.
echo Nombre 09:
set /a "Octal1=09"
echo.
pause

III - Les conditions

Les conditions sont supportées par la commande if. Elle permet d'effectuer des comparaisons de toutes sortes et d'exécuter des commandes en fonction du résultat de cette comparaison. else et else if sont, quant à eux, des paramètres de la commande if et ne peuvent être utilisés qu'à l'intérieur de la commande if.

III-A - If, else, else if, not et /i

La commande if se traduit simplement par « si » : « si » la condition est vraie, fais ceci ; elle peut être accompagnée par le paramètre else qui se traduit par « sinon » : « si » la condition est vraie, fais ceci, « sinon » fais cela. La syntaxe de la commande if est la suivante.

if <condition> <commande1> [else <commande2>]

Comme vous pouvez le voir sur la syntaxe de la commande if, if vient en premier suivi de sa <condition> et d'une commande (<commande1>), puis vient else et une commande (<commande2>), on peut le traduire par :

- si (if) la <condition> se vérifie alors la <commande1> s'exécute ;

- sinon (else) c'est la <commande2> qui s'exécute.

Le paramètre else ne peut être utilisé seul, il doit toujours être utilisé avec la commande if, et être sur la même ligne. Il y a deux règles importantes à garder en tête lorsqu'on utilise des conditions :

- la première est que l'on ne peut utiliser qu'un maximum de 2048 caractères (sous Windows XP et inférieur) ou un peu plus de 4096 caractères (sous Windows Vista et supérieur) par ligne de commande, limite très vite atteinte avec plusieurs conditions, plus leurs commandes et leurs paramètres ;

- et la deuxième est que les parenthèses sont prises en compte par l'interpréteur de commande comme des opérateurs de bloc, c'est pour cela qu'il faut les échapper lorsque l'on ne s'en sert pas à cette fin.

Les opérateurs de bloc permettent d'utiliser un bloc de commande en lieu et place d'une commande. Ainsi, chaque fois que la syntaxe autorise l'utilisation d'une commande, celle-ci peut être remplacée par un bloc de commandes.

Ainsi la syntaxe de la commande if peut être modifiée de la manière suivante.

Syntaxe des conditions if et else avec parenthèses

if <condition> (
<commande1>
<commande2>
) else (
<commande3>
<commande4>
)

L'utilisation des parenthèses comme opérateurs de bloc permet d'exécuter plusieurs commandes, par condition vérifiée, au lieu d'une seule. La parenthèse ouvrante associée à une condition doit se trouver sur la même ligne que cette dernière ; si une autre condition est utilisée conjointement, alors elle doit se trouver sur la même ligne que la parenthèse fermante associée à la condition qui la précède.

La commande if autorise aussi le paramètre else if qui se traduit par « sinon si », ce dernier permet de poser une condition supplémentaire avant l'exécution éventuelle du else. La syntaxe serait la suivante.

if <condition1> <cmd1> [else if <condition2> <cmd2>] [else <cmd3>]

Les opérateurs de bloc fonctionnent de la même manière avec le paramètre else if en plus. Il faut aussi noter que le paramètre else if peut être utilisé autant de fois que l'on veut dans la commande if.

Syntaxe des conditions if, else if et else

if <condition1> (
<commande1>
<commande2>
) else if <condition2> (
<commande3>
<commande4>
) else if <condition3> (
<commande5>
<commande6>
) else (
<commande7>
<commande8>
)

La syntaxe ci-dessus pourrait être traduite par :

- si (if) la <condition1> est vraie : exécuter <commande1> et <commande2> ;

- sinon si (else if) <condition2> est vraie : exécuter <commande3> et <commande4> ;

- sinon si (else if) <condition3> est vraie : exécuter <commande5> et <commande6> ;

- sinon (else) : exécuter <commande7> et <commande8>.

Les conditions associées à la commande if et au paramètre else if prennent le modificateur not qui permet d'exécuter une commande si la condition est fausse. Le modificateur not ne s'applique pas à toutes les conditions de la même structure de contrôle. Si le paramètre else if est utilisé conjointement, son comportement dépendra de la présence, ou non, du modificateur not à l'intérieur de la condition.

if not <condition1> <cmd1> else if <condition2> <cmd2> else <cmd3>

La commande ci-dessus se traduit par :

- si (if) la <condition1> est fausse : exécuter la <cmd1> ;

- sinon (else if), si la <condition2> est vraie : exécuter la <cmd2> ;

- sinon (else), si la <condition1> est vraie et que la <condition2> est fausse, exécuter la <cmd3>.

Les conditions associées à la commande if et au paramètre else if prennent le modificateur /i qui permet de ne pas tenir compte de la casse lorsque la condition traite des chaînes de caractères. Ce modificateur ne fonctionne que sur les conditions qui traitent des chaînes de caractères. Si le paramètre else if est utilisé conjointement, son comportement dépendra de la présence, ou non, du modificateur /i à l'intérieur de la condition.

if /i <condition1> <cmd1> else if /i <condition2> <cmd2> else <cmd3>

Se traduit par :

- si (if) la <condition1> est vraie, sans tenir compte de la casse, exécuter la <cmd1> ;

- sinon si (else if) la <condition2> est vraie, sans tenir compte de la casse, exécuter la <cmd2> ;

- sinon (else), si la <condition1> est fausse sans tenir compte de la casse et que la <condition2> est fausse sans tenir compte de la casse, exécuter la <cmd3>.

Nous allons maintenant voir quelles sont les conditions supportées par la commande if et le paramètre else if.

III-B - La condition « exist <path> »

La condition exist <path> permet de tester si un chemin d'accès ou un fichier existe. Elle peut être utilisée avec le paramètre else if et le modificateur not. Le modificateur /i ne fonctionne pas sur cette condition. Sa syntaxe est la suivante, où <path> est le chemin d'accès ou le fichier à tester. Si le chemin d'accès contient des espaces, il doit être placé entre guillemets.

if [not] exist ["]<path>["] <commande>

Exemple avec le script 32.

Script 32

1. @echo off

2. if exist "%cd%" echo %cd% existe bien.

3. pause

III-C - La condition « defined <variable> »

La condition defined <variable> permet de tester si une variable a une valeur définie. Elle peut être utilisée avec le paramètre else if, et le modificateur not. Le modificateur /i ne fonctionne pas sur cette condition. Voir le script 33.

if [not] defined <variable> <commande>

Script 33

1. @echo off

2. set "Def1="

3. set "Def2=abcd"

4. if defined Def1 (

5. echo Def1 est définie.

6. ) else (

7. echo Def1 n'est pas définie.

8. )

9. if defined Def2 (

10. echo Def2 est définie.

11. ) else (

12. echo Def2 n'est pas définie.

13. )

14. if not defined Def3 (

15. echo Def3 n'est pas définie.

16. ) else (

17. echo Def3 est définie.

18. )

19. pause

III-D - La condition « errorlevel <opérande> »

La condition errorlevel <opérande> permet de tester si le code d'erreur de la dernière commande exécutée est égal ou supérieur au nombre donné en <opérande>. Le code d'erreur est un nombre renvoyé par une commande pour donner des informations sur le déroulement de son exécution, il est aussi accessible via la variable ErrorLevel. En général, le code d'erreur est 0 si aucune erreur ne s'est produite ; et il est égal ou supérieur à 1 si une erreur s'est produite, chaque code d'erreur correspondant à une erreur précise. Le nom de variable ErrorLevel est un nom de variable réservé, vous pouvez la modifier en l'écrasant avec une nouvelle valeur, cependant, elle prendra la valeur donnée jusqu'à la fin du script. Attention toutefois, il arrive que des commandes ne modifient pas la variable ErrorLevel ou bien qu'elle lui donne toujours le code 0, ceci est souvent dû à de mauvaises pratiques de programmation, fiez-vous à des codes d'erreurs connues et ayant une valeur concrète, ne traitez pas les autres, pour ce faire, consultez le manuel ou l'aide de la commande concernée. Si le modificateur not est utilisé, la <commande> seras exécutée seulement si ErrorLevel est inférieur à l'<opérande>. Le modificateur /i n'est pas supporté par la condition errorlevel <opérande>.

if [not] errorlevel <opérande> <commande>

Script 34

1. @echo off

2. rem La commande « setlocal » crée un environnement local pour ne pas générer d'erreur si le script est appelé depuis l'interpréteur.

3. setlocal

4. set "TestError1=Un petit test"

5. if not errorlevel 1 echo Aucune erreur dans la première commande.

6. echo.

7. set /a "TestError2=09"

8. echo.

9. if errorlevel 1 echo Le code d'erreur des nombres invalides est %ErrorLevel%.

10. pause

11. rem  La commande « endlocal » supprime l'environnement local.

12. endlocal

III-E - La condition « cmdextversion <opérande> »

La condition cmdextversion <opérande> permet de tester si le numéro de version des extensions de commande est égal ou supérieur au nombre donné en <opérande>. Le numéro de version des extensions de commande est à prendre en compte, car selon la version des extensions, le traitement des commandes peut être modifié. Reportez-vous à l'aide concernant les commandes citées dans le tableau 7 pour plus de détails. Chaque commande modifiée l'est d'une manière qui lui est propre ; ainsi la commande assoc ne sera pas modifiée de la même manière que la commande start, tout simplement parce qu'elle n'exécute pas du tout la même opération.

DEL ou ERASE

COLOR

CD ou CHDIR

MD ou MKDIR

PROMPT

PUSHD

POPD

SET

SETLOCAL

ENDLOCAL

IF

FOR

CALL

SHIFT

GOTO

START

ASSOC

FTYPE

Tableau 7 : Commandes soumises aux extensions

La condition cmdextversion <opérande> est toujours fausse si les extensions de commande sont désactivées ; la première version des extensions est la version « 1 » et le numéro de version est incrémenté de 1 à chaque nouvelle version. Si le numéro de version des extensions est égal ou supérieur à la valeur donnée dans la condition cmdextversion <opérande> alors la condition est vraie ; les extensions de commande étant rétrocompatibles d'une version à l'autre. Si le modificateur not est utilisé, la condition est vraie si le numéro de version des extensions de commande est inférieur au nombre donné en <opérande>. La condition cmdextversion <opérande> ne prend pas le modificateur /i. Le tableau 8 donne la liste des révisions d'extensions de commande en fonction du système d'exploitation.

Numéro de version

Compatibilité

1

Windows 2000.

2

Tous les systèmes NT XP et supérieur.

Tableau 8 : Révisions des extensions de commande

if [not] cmdextversion <opérande> <commande>

Script 35

1. @echo off

2. rem La commande « setlocal enableextensions » active les extensions de commande.

3. setlocal enableextensions

4. if not cmdextversion 1 (

5. echo CmdExtVersion 1:  Not Detected.

6. ) else (

7. echo CmdExtVersion 1:  Detected.

8. )

9. if cmdextversion 2 (

10. echo CmdExtVersion 2:  Detected.

11. ) else (

12. echo CmdExtVersion 2:  Not Detected.

13. )

14. if cmdextversion 3 (

15. echo CmdExtVersion 3:  Detected.

16. ) else (

17. echo CmdExtVersion 3:  Not Detected.

18. )

19. pause

III-F - La condition « <chaîne1> <comparateur> <chaîne2> »

La condition <chaîne1> <comparateur> <chaîne2> permet d'effectuer des comparaisons sur des chaînes et des nombres. La comparaison de chaînes est effectuée au moyen d'un XOR au niveau du bit entre chaque octet d'une chaîne puis les résultats obtenus sur les différentes chaînes sont comparés numériquement. Si les deux chaînes ne sont constituées que de chiffres alors elles sont transtypées en nombre puis évaluées numériquement. Les comparateurs, pris en compte par la condition <chaîne1> <comparateur> <chaîne2>, sont listés dans le tableau 9. La condition <chaîne1> <comparateur> <chaîne2> prend les modificateurs not et /i. Sa syntaxe est la suivante, si les chaînes contiennent des espaces, elles doivent être placées entre guillemets.

if [/i] [not] ["]<chaîne1>["] <comparateur> ["]<chaîne2>["] <cmd>

Comparateur

Description

EQU

« EQUal », la condition est vraie si les deux chaînes sont égales.

NEQ

« Not EQual », la condition est vraie si les deux chaînes sont différentes.

LSS

« LeSS », la condition est vraie si chaîne1 est inférieure à chaîne2.

LEQ

« Less or EQual », la condition est vraie si chaîne1 est inférieure ou égale à chaîne2.

GTR

« GreaTeR », la condition est vraie si chaîne1 est supérieure à chaîne2.

GEQ

« Greater or EQual », la condition est vraie si chaîne1 est supérieure ou égale à chaîne2.

Tableau 9 : Opérateurs de comparaison

Script 36

1. @echo off

2. cls

3. set "Chaîne1=Un Petit test"

4. rem Encodage de la Chaîne1 :55 6E 20 50 65 74 69 74 20 74 65 73 74

5. set /a "C1X=0x55^0x6E^0x20^0x50^0x65^0x74^0x69^0x74^0x20^0x74^0x65^0x73^0x74"

6. set "Chaîne2=Un petit test"

7. rem Encodage de la Chaîne2 : 55 6E 20 70 65 74 69 74 20 74 65 73 74

8. set /a "C2X=0x55^0x6E^0x20^0x70^0x65^0x74^0x69^0x74^0x20^0x74^0x65^0x73^0x74"

9. set "Chaîne3=Un grand test"

10. rem Encodage de la Chaîne3 : 55 6E 20 67 72 61 6E 64 20 74 65 73 74

11. set /a "C3X=0x55^0x6E^0x20^0x67^0x72^0x62^0x6E^0x64^0x20^0x74^0x65^0x73^0x74"

12. set "Chaîne4=41"

13. set "Chaîne5=12"

14. echo               Chaîne 1         Chaîne 2          Chaîne 3

15. echo =============================================================

16. echo Chaîne        %Chaîne1%    %Chaîne2%    %Chaîne3%

17. echo Évaluation    %C1X%              %C2X%               %C3X%

18. echo.

19. if "%Chaîne1%" GTR "%Chaîne2%" (

20. echo "%Chaîne1%" est supérieur à "%Chaîne2%".

21. ) else if "%Chaîne1%" EQU "%Chaîne2%" (

22. echo "%Chaîne1%" est égal à "%Chaîne2%".

23. ) else if "%Chaîne1%" LSS "%Chaîne2%" (

24. echo "%Chaîne1%" est inférieur à "%Chaîne2%".

25. )

26. if /i "%Chaîne1%" GTR "%Chaîne2%" (

27. echo "%Chaîne1%" est supérieur à "%Chaîne2%".

28. ) else if /i "%Chaîne1%" EQU "%Chaîne2%" (

29. echo "%Chaîne1%" est égal à "%Chaîne2%".

30. ) else if /i "%Chaîne1%" LSS "%Chaîne2%" (

31. echo "%Chaîne1%" est inférieur à "%Chaîne2%".

32. )

33. if "%Chaîne1%" GTR "%Chaîne3%" (

34. echo "%Chaîne1%" est supérieur à "%Chaîne3%".

35. ) else if "%Chaîne1%" EQU "%Chaîne3%" (

36. echo "%Chaîne1%" est égal à "%Chaîne3%".

37. ) else if "%Chaîne1%" LSS "%Chaîne3%" (

38. echo "%Chaîne1%" est inférieur à "%Chaîne3%".

39. )

40. if /i "%Chaîne1%" GTR "%Chaîne3%" (

41. echo "%Chaîne1%" est supérieur à "%Chaîne3%".

42. ) else if /i "%Chaîne1%" EQU "%Chaîne3%" (

43. echo "%Chaîne1%" est égal à "%Chaîne3%".

44. ) else if /i "%Chaîne1%" LSS "%Chaîne3%" (

45. echo "%Chaîne1%" est inférieur à "%Chaîne3%".

46. )

47. if "%Chaîne3%" GTR "%Chaîne2%" (

48. echo "%Chaîne3%" est supérieur à "%Chaîne2%".

49. ) else if "%Chaîne3%" EQU "%Chaîne2%" (

50. echo "%Chaîne3%" est égal à "%Chaîne2%".

51. ) else if "%Chaîne3%" LSS "%Chaîne2%" (

52. echo "%Chaîne3%" est inférieur à "%Chaîne2%".

53. )

54. if /i "%Chaîne3%" GTR "%Chaîne2%" (

55. echo "%Chaîne3%" est supérieur à "%Chaîne2%".

56. ) else if /i "%Chaîne3%" EQU "%Chaîne2%" (

57. echo "%Chaîne3%" est égal à "%Chaîne2%".

58. ) else if /i "%Chaîne3%" LSS "%Chaîne2%" (

59. echo "%Chaîne3%" est inférieur à "%Chaîne2%".

60. )

61. echo.

62. echo.

63. echo               Chaîne 4    Chaîne 5

64. echo =============================================================

65. echo Chaîne        %Chaîne4%          %Chaîne5%

66. echo.

67. if %Chaîne4% GTR %Chaîne5% (

68. echo %Chaîne4% est supérieur à %Chaîne5%.

69. ) else if %Chaîne4% EQU %Chaîne5% (

70. echo %Chaîne4% est égal à %Chaîne5%.

71. ) else if %Chaîne4% LSS %Chaîne5% (

72. echo %Chaîne4% est inférieur à %Chaîne5%.

73. )

74. echo.

75. pause

III-G - La condition « "<chaîne1>"=="<chaîne2>" »

La condition "<chaîne1>"=="<chaîne2>" permet de tester une égalité entre des chaînes de caractères. Les chaînes doivent être placées entre guillemets si elles sont susceptibles de contenir des espaces. Cette condition prend les modificateurs not et /i.

Script 37

1. @echo off

2. cls

3. set "Un=Une Chaîne"

4. set "Deux=une chaîne"

5. if "%Un%"=="%Deux%" (

6. echo If:      "%Un%"    est égale à    "%Deux%".

7. ) else (

8. echo If:      "%Un%" n'est pas égale à "%Deux%".

9. )

10. if not "%Un%"=="%Deux%" (

11. echo If Not:  "%Un%" n'est pas égale à "%Deux%".

12. ) else (

13. echo If Not:  "%Un%"    est égale à    "%Deux%".

14. )

15. if /i "%Un%"=="%Deux%" (

16. echo If /i:   "%Un%"    est égale à    "%Deux%".

17. ) else (

18. echo If /i:   "%Un%" n'est pas égale à "%Deux%".

19. )

20. pause

III-H - Mise en application de la commande « if »

Dans cette section, nous allons reprendre le script 29 qui permettait de transformer un entier en sa représentation hexadécimale en notation en complément à deux. Grâce à la commande if, nous allons faire en sorte de traiter les nombres négatifs et positifs avec la même fonction de calcul et d'afficher le résultat dans une forme hexadécimale correcte.

Script 38

1. @echo off

2. rem On crée un environnement local pour ne pas générer d'erreur.

3. setlocal

4. rem On récupère un nombre sous forme de chaîne et on le place dans SInt32.

5. set /p "SInt32=Entrez un nombre entre -2147483648 et 2147483647:    "

6. rem On transtype SInt32 en nombre pour qu'il soit évalué correctement.

7. set /a "SInt32"

8. rem On utilise une variable témoin pour savoir si le nombre est

9. rem négatif ou pas sans avoir à le réévaluer à chaque fois.

10. rem Par défaut, on considère que le nombre est positif.

11. set "NegNum=false"

12. rem Si SInt32 est inférieur à zéro, il est négatif,

13. rem     alors on effectue un NON au niveau du bit pour obtenir son complément à un

14. rem     et on met NegNum à "true" pour définir le nombre comme négatif.

15. if %SInt32% LSS 0 (

16. set /a "SInt32=~SInt32"

17. set "NegNum=true"

18. )

19. rem On utilise un algorithme de divisions successives par 16 pour obtenir des

20. rem nombres en base 16, chaque nombre obtenu forme un quartet.

21. set /a "Nibble0= SInt32 %% 16","HighOrder0= SInt32 / 16"

22. set /a "Nibble1=HighOrder0 %% 16","HighOrder1=HighOrder0 / 16"

23. set /a "Nibble2=HighOrder1 %% 16","HighOrder2=HighOrder1 / 16"

24. set /a "Nibble3=HighOrder2 %% 16","HighOrder3=HighOrder2 / 16"

25. set /a "Nibble4=HighOrder3 %% 16","HighOrder4=HighOrder3 / 16"

26. set /a "Nibble5=HighOrder4 %% 16","HighOrder5=HighOrder4 / 16"

27. set /a "Nibble6=HighOrder5 %% 16"

28. set /a "Nibble7=HighOrder5 / 16"

29. rem Si c'est un nombre négatif ^("%NegNum%"=="true"^),

30. rem     on retranche chaque valeur à 15 pour récupérer le nombre original.

31. if "%NegNum%"=="true" (

32. set /a "Nibble0=15 - Nibble0"

33. set /a "Nibble1=15 - Nibble1"

34. set /a "Nibble2=15 - Nibble2"

35. set /a "Nibble3=15 - Nibble3"

36. set /a "Nibble4=15 - Nibble4"

37. set /a "Nibble5=15 - Nibble5"

38. set /a "Nibble6=15 - Nibble6"

39. set /a "Nibble7=15 - Nibble7"

40. )

41. rem On transforme chaque quartet en notation hexadécimale.

42. if %Nibble0% GTR 9 (

43. if %Nibble0% EQU 10 set "Nibble0=A"

44. if %Nibble0% EQU 11 set "Nibble0=B"

45. if %Nibble0% EQU 12 set "Nibble0=C"

46. if %Nibble0% EQU 13 set "Nibble0=D"

47. if %Nibble0% EQU 14 set "Nibble0=E"

48. if %Nibble0% EQU 15 set "Nibble0=F"

49. )

50. if %Nibble1% GTR 9 (

51. if %Nibble1% EQU 10 set "Nibble1=A"

52. if %Nibble1% EQU 11 set "Nibble1=B"

53. if %Nibble1% EQU 12 set "Nibble1=C"

54. if %Nibble1% EQU 13 set "Nibble1=D"

55. if %Nibble1% EQU 14 set "Nibble1=E"

56. if %Nibble1% EQU 15 set "Nibble1=F"

57. )

58. if %Nibble2% GTR 9 (

59. if %Nibble2% EQU 10 set "Nibble2=A"

60. if %Nibble2% EQU 11 set "Nibble2=B"

61. if %Nibble2% EQU 12 set "Nibble2=C"

62. if %Nibble2% EQU 13 set "Nibble2=D"

63. if %Nibble2% EQU 14 set "Nibble2=E"

64. if %Nibble2% EQU 15 set "Nibble2=F"

65. )

66. if %Nibble3% GTR 9 (

67. if %Nibble3% EQU 10 set "Nibble3=A"

68. if %Nibble3% EQU 11 set "Nibble3=B"

69. if %Nibble3% EQU 12 set "Nibble3=C"

70. if %Nibble3% EQU 13 set "Nibble3=D"

71. if %Nibble3% EQU 14 set "Nibble3=E"

72. if %Nibble3% EQU 15 set "Nibble3=F"

73. )

74. if %Nibble4% GTR 9 (

75. if %Nibble4% EQU 10 set "Nibble4=A"

76. if %Nibble4% EQU 11 set "Nibble4=B"

77. if %Nibble4% EQU 12 set "Nibble4=C"

78. if %Nibble4% EQU 13 set "Nibble4=D"

79. if %Nibble4% EQU 14 set "Nibble4=E"

80. if %Nibble4% EQU 15 set "Nibble4=F"

81. )

82. if %Nibble5% GTR 9 (

83. if %Nibble5% EQU 10 set "Nibble5=A"

84. if %Nibble5% EQU 11 set "Nibble5=B"

85. if %Nibble5% EQU 12 set "Nibble5=C"

86. if %Nibble5% EQU 13 set "Nibble5=D"

87. if %Nibble5% EQU 14 set "Nibble5=E"

88. if %Nibble5% EQU 15 set "Nibble5=F"

89. )

90. if %Nibble6% GTR 9 (

91. if %Nibble6% EQU 10 set "Nibble6=A"

92. if %Nibble6% EQU 11 set "Nibble6=B"

93. if %Nibble6% EQU 12 set "Nibble6=C"

94. if %Nibble6% EQU 13 set "Nibble6=D"

95. if %Nibble6% EQU 14 set "Nibble6=E"

96. if %Nibble6% EQU 15 set "Nibble6=F"

97. )

98. if %Nibble7% GTR 9 (

99. if %Nibble7% EQU 10 set "Nibble7=A"

100. if %Nibble7% EQU 11 set "Nibble7=B"

101. if %Nibble7% EQU 12 set "Nibble7=C"

102. if %Nibble7% EQU 13 set "Nibble7=D"

103. if %Nibble7% EQU 14 set "Nibble7=E"

104. if %Nibble7% EQU 15 set "Nibble7=F"

105. )

106. rem Les quartets 0 à 3 forment le Least Significant Word.

107. set "LSW=%Nibble3%%Nibble2%%Nibble1%%Nibble0%"

108. rem Les quartets 4 à 7 forment le Most Significant Word.

109. set "MSW=%Nibble7%%Nibble6%%Nibble5%%Nibble4%"

110. rem on affiche le résultat.

111. echo 0x%MSW%%LSW%

112. rem Fin de l'environnement local.

113. endlocal

114. pause

Ce script sera un script témoin que nous ferons évoluer tout au long de ce document, il nous permettra de comparer les différentes approches et ce qu'elles impliquent comme contrainte d'utilisation.

En son état actuel, ce script est gourmand en ressource système, car à chaque ligne de commande correspond un appel vers l'interpréteur augmentant d'autant son temps de traitement. Les boucles for devraient nous permettre de réduire considérablement son coût en temps d'exécution et la taille du script par la même occasion.

IV - Tout dépend du contexte

Un script batch s'exécute toujours de manière linéaire, du début vers la fin, et à moins que l'on ne redirige son exécution, ce comportement reste inchangé. Il existe plusieurs possibilités pour rediriger l'exécution d'un script telles que : les labels, les sauts, les appels de fonction et les sorties.

IV-A - Les labels

Les labels sont des adresses relatives se présentant sous forme de chaînes de caractères préfixées par : et terminées par un caractère blanc (l'espace, la tabulation ou le retour à la ligne). Ces adresses relatives pointent vers le premier caractère situé après le retour à la ligne qui termine le label. Elles peuvent être utilisées pour adresser une portion de code. Chaque nom de label devrait être unique dans le script. La syntaxe d'un label est la suivante, où <Label>est le nom du label :

:<Label>

IV-B - Les sauts

La commande goto effectue un saut inconditionnel vers le label spécifié en paramètre, permettant ainsi de continuer l'exécution du programme dans une portion de code située n'importe où dans le script. Une fois le saut effectué, l'exécution continue à la ligne qui suit le label spécifié :

goto [:]<Label>

Script 39

1. @echo off

3. goto :MonLabel

4. echo Une phrase qui ne sera jamais affichée.

6. :MonLabel

7. echo Une phrase qui sera affichée.

8. pause

Dans le script 39, la commande echo Une phrase qui ne sera jamais affichée. n'est pas traitée, l'exécution étant redirigée par la commande goto :MonLabel vers la ligne suivant le label :MonLabel soit la commande echo Une phrase qui sera affichée.

IV-C - Contexte de commande

L'exécution de l'interpréteur ou d'un script s'effectue dans un processus hôte auquel le système d'exploitation alloue un espace mémoire. Cet espace mémoire est appelé la pile. Une pile est une zone de mémoire dans laquelle les données sont placées les unes à la suite des autres. Les données qui y sont placées doivent être récupérées dans un ordre particulier : la dernière donnée placée doit être la première à être récupérée. Sur cette pile, le système y « place » ce que l'on appelle un contexte.

Lors de sa création, le contexte se voit attribuer, par le système, une série de variables : les variables d'environnement du système, des variables contenant la commande et les paramètres de la commande ayant généré le contexte et une adresse de sortie du contexte. Ainsi créé, le contexte représente l'environnement dans lequel le script va s'exécuter en lui permettant de disposer de données qui lui sont propres. À chaque nouvelle exécution d'un script ou d'une commande, l'interpréteur crée un nouveau contexte et le place sur la pile. Ce nouveau contexte est appelé contexte descendant.

Les variables d'environnement sont héritées du système lors de la création du processus hôte et se propagent par ascendance successive aux différents contextes descendants. Les variables contenant la commande et les arguments d'appels, elles, sont propres au contexte et ne se propagent jamais aux contextes descendants. L'adresse de sortie du contexte n'est pas accessible en tant que variable et, tout comme les arguments d'appel, est propre au contexte. Son rôle est plus amplement détaillé dans le reste du chapitre.

Certaines commandes s'exécutent dans le contexte en cours alors que d'autres créent leurs propres contextes. C'est, en partie, ce qui fait la différence entre commande interne et externe : les commandes internes sont en fait des fonctions internes de l'interpréteur qu'il est possible d'appeler via un alias de type « nom de commande », alors que les commandes externes sont des exécutables distincts de l'interpréteur. L'exécution d'une commande externe ou d'un script générera toujours un nouveau contexte. Les commandes internes s'exécutent toujours dans le contexte courant. Il faut cependant noter que les commandes internes, même si elles exécutent toujours leurs fonctions principales dans le contexte courant, peuvent créer des contextes descendants afin d'exécuter certaines fonctions comme la boucle for qui génère un contexte initial dans lequel se trouve l'ensemble à traiter et un contexte secondaire, créé à chaque itération de la boucle, dans lequel s'opère le traitement.

IV-C-1 - La portée des variables

Si le concept de contexte de commande est si important, c'est qu'il influe grandement sur l'utilisation des variables. En effet, chaque contexte est une « fraction » de la pile qui n'est pas accessible lorsque l'exécution s'opère depuis un contexte ascendant. Les variables se propagent par ascendance ; c'est-à-dire que lorsqu'un nouveau contexte est créé, il hérite des variables (et de leurs valeurs) du contexte ascendant (celui à partir duquel il a été créé). Si une variable est modifiée dans un contexte descendant, sa valeur restera inchangée dans le contexte ascendant. Ainsi, faire passer une variable vers un contexte descendant est simple (il suffit de créer le contexte), mais l'inverse s'avère plus compliqué, car il n'est pas possible de modifier une valeur dans un contexte ascendant.

L'interpréteur supporte l'expansion retardée des variables qui permet, dans une certaine mesure, de faire passer une valeur vers un contexte ascendant. Pour cela, le processus hôte, lorsque l'expansion retardée est activée, alloue une seconde zone de mémoire qui prend la forme d'un tas qui est accessible depuis n'importe quel contexte. Contrairement à la pile, il est possible, avec le tas, d'y placer et récupérer les données dans n'importe quel ordre. À chaque fois qu'une variable est créée ou modifiée, elle est placée à la fois dans le contexte (autrement dit sur la pile) et dans le tas. Pour accéder aux variables dont l'expansion est retardée, il faut utiliser le symbole ! au lieu du symbole % lors de leur expansion. Ainsi la variable prendra la dernière valeur qui lui a été attribuée pendant l'exécution et non la valeur qu'elle possède dans le contexte en cours. L'expansion retardée des variables sera plus amplement abordée dans le chapitre VI.

IV-D - Les fonctions

Les fonctions sont des portions de code « isolées », commençant par un label et finissant par un saut à une adresse spécifique : l'adresse de retour. La vraie différence avec une simple portion de code réside dans le fait qu'un nouveau contexte est créé, permettant ainsi aux fonctions de disposer de paramètres de commande et, dans certains cas, d'un code de sortie.

IV-D-1 - L'appel de fonction

La commande call permet d'effectuer des appels de fonction. Elle a la particularité de créer un contexte dans lequel va s'exécuter la fonction appelée (la portion de code) ; lors de la création du contexte descendant, une adresse de retour et les arguments d'appel vont être empilés. La syntaxe de la commande call est la suivante, où <Label> est le nom de la fonction à appeler et <paramètre> est le ou les paramètres à passer à la fonction.

call :<label> [<paramètre> [...]]

Lors d'un appel à une fonction, si l'exécution est redirigée vers une autre adresse que l'adresse de retour et que, par la suite, d'autres appels sont effectués vers cette même fonction, alors la première adresse ne sera pas dépilée, occasionnant une fuite de mémoire. Si un script comporte ce cas, son exécution peut être stoppée par l'interpréteur (si l'utilisation de la pile atteint 90 %) comme le montre le script 40.

Script 40

1. @echo off

2. :LabelUn

3. call :LabelDeux

4. :LabelDeux

5. goto :LabelUn

IV-D-2 - Sortie de fonction

La commande goto prend aussi le label :eof (End Of File), qui prend la valeur de l'adresse de retour du contexte en cours, soit la ligne se trouvant immédiatement après le dernier appel effectué.

La commande goto n'ajoute rien dans la pile, ainsi l'exécution d'un goto ne peut occasionner de fuite de mémoire. Dans le cas précis de l'exécution d'un goto :eof, la commande goto incrémente le pointeur de pile de la taille totale des adresses des arguments d'appel et de la taille de l'adresse de retour, permettant à la pile de revenir à l'état d'avant l'appel, puis effectue un saut inconditionnel à l'adresse de retour, soit la ligne suivant le dernier appel. Ainsi, si l'on modifie le script 40 de la manière suivante (script 41), on obtient un script qui ne finit jamais, car il n'y a ni fuite de mémoire (chaque appel de la fonction :LabelDeux finit par goto :eof), ni point de sortie (il y aura toujours une commande à exécuter, l'exécution revient toujours au :LabelUn). Seul l'appui sur les touches Ctrl+C permet de quitter le script.

Script 41

1. @echo off

2. rem On ajoute un compteur pour visualiser le parcours effectué.

3. set /a "Counter=0"

4. :LabelUn

5. call :LabelDeux

6. set /a "Counter+=1"

7. goto :LabelUn

8. :LabelDeux

9. echo %Counter%

10. goto :eof

IV-E - Les paramètres de commande

Ils sont passés au contexte lors de sa création et sont accessibles sous la forme de variables spéciales : %n, n étant le numéro d'index du paramètre. En effet, chaque contexte étant généré suite à l'appel d'un script, d'une fonction ou d'une commande, il possède une commande d'appel et, la plupart du temps, de paramètres d'appel. L'index %0 contient le nom du script, de la fonction ou de la commande, l'index %1 contient le paramètre 1, l'index %2 contient le paramètre 2 et ainsi de suite. Le paramètre %* expanse tous les paramètres passés au contexte (%0 n'est pas un paramètre, mais une commande d'appel et n'est donc pas renvoyée par %*). Exemple avec le script 42 :

Script 42

1. @echo off

3. call :MonLabel Param1 Param2

4. pause

5. rem Si tous les contextes créés pendant l'exécution du script ont

6. rem correctement été dépilés, alors le label «:eof » prend comme

7. rem valeur l'adresse de sortie du processus hôte.

8. goto :eof

10. :MonLabel

11. echo %*

12. echo %0

13. echo %1

14. echo %2

15. goto :eof

L'utilisation des paramètres suit plusieurs règles :

- les paramètres ne sont pas limités en nombre dans la commande d'appel, mais la règle des 2048/4096 caractères maximum par ligne de commande s'applique ;

- par souci de compatibilité avec les systèmes Windows XP et antérieurs, il est préférable de ne pas utiliser des paramètres avec un index supérieur à 9, qui restent cependant accessibles via la commande shift (voir section IV.E.2).

IV-E-1 - Les modificateurs de paramètres de commande

Les paramètres de commande supportent plusieurs modificateurs permettant de les parser, en voici la liste exhaustive :

Modificateur

Description

%~1

renvoie %1 en supprimant les guillemets (") de début et de fin.

%~f1

renvoie le chemin d'accès vérifié du fichier désigné par %1, si le fichier n'est pas trouvé, alors ce modificateur s'expanse en une chaîne vide.

%~d1

renvoie la lettre de lecteur du fichier désigné par %1.

%~p1

renvoie le chemin d'accès du fichier désigné par %1.

%~n1

renvoie le nom du fichier désigné par %1.

%~x1

renvoie l'extension du fichier désigné par %1.

%~s1

renvoie le chemin d'accès, sous forme de noms courts, du fichier désigné par %1.

%~a1

renvoie les attributs du fichier désigné par %1.

%~t1

renvoie la date et l'heure de création du fichier désigné par %1.

%~z1

renvoie la taille du fichier désigné par %1.

%~$<variable>:1

<variable> est parcourue à la recherche d'occurrence de %1, si aucune occurrence de %1 n'est trouvée ou que <variable> n'est pas défini dans le contexte en cours, alors ce modificateur s'expanse en une chaîne vide. Si <variable> est composé de plusieurs chaînes, elles doivent être séparées par des points-virgules (;) afin que le modificateur puisse les distinguer correctement.

Tableau 10 : Les modificateurs de paramètres de commande.

Les modificateurs de paramètres de commande fonctionnent pour tous les index. Ils peuvent être utilisés conjointement pour obtenir plusieurs informations en même temps, comme le montre le script 43 :

Script 43

1. @echo off

2. rem Ce script doit être lancé depuis « cmd.exe » pour obtenir le bon résultat.

3. cls

4. call :MonLabel %cmdcmdline%

5. pause

6. goto :eof

7. :MonLabel

8. echo %~dpnx$ComSpec:1

9. goto :eof

IV-E-2 - La commande shift

La commande shift permet de décaler l'index de tous les paramètres de -1, afin d'accéder aux index supérieurs à neuf, ou de créer un mécanisme de gestion des paramètres (voir la mise en application de ce chapitre). Si les extensions de commande sont activées, la commande shift prend le paramètre /n, où n est le numéro d'index à partir duquel commence le décalage des paramètres, tous les index égaux ou supérieurs à n seront décalés de -1.

shift [/n]

Dans le script 44, le commande shift /1 permet d'accéder au deuxième paramètre via l'index 1 :.

Script 44

1. @echo off

2. cls

3. call :MonLabel "rien" %cmdcmdline%

4. pause

5. exit /b

6. :MonLabel

7. shift /1

8. echo %~dpnx$ComSpec:1

9. goto :eof

IV-F - Les sorties

La sortie d'un contexte ou d'un processus peut s'effectuer via les commandes exit et goto :eof. Dans le cas de goto :eof, si tous les contextes ont correctement été dépilés, alors la dernière adresse dépilée est :

- soit l'adresse de sortie du processus dans le cas d'un script appelé en cliquant dessus ;

- soit l'adresse de retour à l'interpréteur si le script a été lancé depuis celui-ci.

La commande exit, elle, permet de sortir du processus hôte, quel que soit le nombre de contextes empilés. Si la commande exit est utilisée avec le paramètre /b, alors la sortie s'effectuera à l'adresse de sortie du contexte et non à l'adresse de sortie du processus hôte. L'intérêt de la commande exit est qu'un code numérique de sortie peut être spécifié, contrairement à goto :eof. La syntaxe de la commande exit est la suivante, où <CodeSortie> est le code de sortie.

exit [/b] [<CodeSortie>]

Dans le script 45, la sortie de la fonction :LabelDeux s'effectue à l'aide d'un goto:eof et la sortie de la fonction :LabelQuatre s'effectue à l'aide d'exit /b ; dans les deux cas, le retour vers la procédure appelante s'opère correctement, car c'est la bonne adresse de retour qui est dépilée. Dans la fonction :LabelSix du script 45, la commande exit /b provoque la fin du script, car la fonction :LabelSix a été appelée avec la commande goto :LabelSix qui n'a rien empilé. Comme tous les contextes précédents ont été dépilés correctement, seule la dernière adresse de retour subsiste. Cette adresse correspond à l'adresse de retour à l'interpréteur si le script a été appelé depuis celui-ci ou à l'adresse de sortie du processus, si le script a été appelé par un double-clic.

Script 45

1. @echo off

2. cls

3. set /a "CounterGoto=0"

4. set /a "CounterExit=0"

5. set /a "CounterCounter=0"

6. :LabelUn

7. call :LabelDeux

8. set /a "CounterGoto+=1"

9. echo CounterGoto: %CounterGoto% sur affichage Goto 1.

10. if %CounterGoto% EQU 6 goto :LabelTrois

11. goto :LabelUn

12. :LabelDeux

13. set /a "CounterGoto+=1"

14. echo CounterGoto: %CounterGoto% sur affichage Goto 2.

15. goto :eof

17. :LabelTrois

18. call :LabelQuatre

19. set /a "CounterExit+=1"

20. echo CounterExit: %CounterExit% sur affichage Exit 1.

21. if %CounterExit% EQU 6 goto :LabelCinq

22. goto :LabelTrois

23. :LabelQuatre

24. set /a "CounterExit+=1"

25. echo CounterExit: %CounterExit% sur affichage Exit 2.

26. exit /b

28. :LabelCinq

29. goto :LabelSix

30. set /a "CounterCounter+=1"

31. echo CounterCounter: %CounterCounter% sur affichage Counter 1.

32. if %CounterCounter% EQU 6 exit /b

33. goto :LabelCinq

34. :LabelSix

35. set /a "CounterCounter+=1"

36. echo CounterCounter: %CounterCounter% sur affichage Counter 2.

37. exit /b

IV-G - Code de sortie

Comme expliqué précédemment, un code de sortie peut être spécifié pour une fonction ou pour un script. Cela se fait via la commande exit [/b] <Code> où <Code> est un code de sortie numérique. Les codes de sortie sont régis par les mêmes règles que les nombres entiers et sont codés sur 32 bits en arithmétique signée. Ce code de sortie est fixé dans la variable ErrorLevel afin de pouvoir être utilisé par la suite. Exemple avec le script 46 :

Script 46

1. @echo off

3. echo %ErrorLevel%

4. call :UnLabel

5. echo %ErrorLevel%

6. pause

7. exit /b

8. :UnLabel

9. exit /b 1

IV-H - Script batch et adresse de retour

L'appel d'un script depuis l'interpréteur ou en cliquant dessus génère toujours un contexte complet. Cependant l'appel d'un script en ligne de commande depuis un autre script génère un contexte ne possédant pas d'adresse de retour. Exemple avec le script 47 :

Script 47

1. @echo off

3. if exist test.bat del /q test.bat

4. rem "^>^>test.bat" est une redirection et sera expliquée au chapitre VII.

5. call :WriteTestBat >>test.bat

7. test.bat

9. echo Retour à %~nx0.

10. del /q test.bat

11. pause

12. exit /b

14. :WriteTestBat

15. echo @echo off

16. echo echo Une phrase affichée dans test.bat

17. echo pause

18. goto :eof

Lorsqu'on exécute le script 47, celui-ci crée un second script, test.bat. L'appel en ligne de commande Test.bat affiche alors Une phrase affichée depuis test.bat et met en pause l'exécution. Lorsqu'on appuie sur une touche, cela provoque la fin des deux scripts. En effet, quand l'interpréteur arrive à la fin d'un script et que celui-ci ne se termine pas par goto :eof ou exit [/b], il effectue de lui-même le saut à l'adresse de retour. Sauf que l'appel de test.bat n'a pas empilé d'adresse de retour et de ce fait, l'adresse qui est dépilée est celle du premier script. Pour parer à ce problème, il faut s'assurer que l'adresse de retour soit effectivement empilée en utilisant la commande call. Ainsi, en modifiant le script 47 de la manière suivante (script 48), l'appel et la sortie s'effectuent sans erreur.

Script 48

1. @echo off

3. if exist test.bat del /q test.bat

4. rem "^>^>test.bat" est une redirection et sera expliquée au chapitre VII.

5. call :WriteTestBat >>test.bat

7. call test.bat

9. echo Retour à %~nx0.

10. del /q test.bat

11. pause

12. exit /b

14. :WriteTestBat

15. echo @echo off

16. echo echo Une phrase affichée dans test.bat

17. echo pause

18. goto :eof

IV-I - La commande start

La commande start permet de lancer un script ou une commande dans un nouveau processus, permettant ainsi d'exécuter des commandes dans un environnement modifié. Ainsi, toute variable créée ou modifiée dans le script appelant est passée à ce nouveau processus.

Syntaxe de la commande start

start ["<caption>"] [/d <path>] [/i] [/min] [/max] [/separate|/shared]
[/low|/normal|/high|/realtime|/abovenormal|/belownormal] [/affinity <hexa>]
[/wait] [/b] [<commande>] [<paramètres>]

Paramètres

Descriptions

<caption>

Titre de la fenêtre.

/d

Spécifie que le chemin d'accès donné par <path> est le répertoire de départ.

<path>

Chemin d'accès du répertoire de départ.

/b

Lance l'application dans la fenêtre courante (en tâche de fond). L'arrêt par Ctrl+C est remplacé par Ctrl+Pause.

/i

Le nouveau contexte sera le contexte original du processus hôte et non le contexte en cours.

/min

Démarrer dans une fenêtre réduite.

/max

Démarrer dans une fenêtre agrandie.

/separate

Démarrer les programmes 16 bits dans un espace mémoire distinct. Ne fonctionne pas sur les systèmes 64 bits.

/shared

Démarrer les programmes 16 bits dans un espace mémoire partagé. Ne fonctionne pas sur les systèmes 64 bits.

/low

Démarrer l'application dans la classe de priorité IDLE.

/normal

Démarrer l'application dans la classe de priorité NORMAL.

/high

Démarrer l'application dans la classe de priorité HIGH.

/realtime

Démarrer l'application dans la classe de priorité REALTIME.

/abovenormal

Démarrer l'application dans la classe de priorité ABOVENORMAL.

/belownormal

Démarrer l'application dans la classe de priorité BELOWNORMAL.

/affinity

La nouvelle application aura le masque d'affinité de processeur spécifié, exprimé en tant que valeur hexadécimale.

<hexa>

Affinité du processus sous forme de valeur hexadécimale.

/wait

Lancer la commande et attendre qu'elle soit finie pour continuer l'exécution. S'il s'agit d'une commande interne ou d'un fichier batch, la fenêtre reste ouverte après l'exécution de la commande.

<commande>

Commande à exécuter.

<paramètres>

Paramètres à passer à la commande.

Tableau 11 : Les paramètres de la commande start.

Le script 49 appelle une nouvelle instance de lui-même avec le paramètre foo après avoir défini la variable X, la seconde instance affiche bien la valeur de X alors qu'elle n'a pas déclaré X. Notez bien qu'il s'agit d'un nouveau processus, il est donc possible de le quitter avec la commande exit sans aucun paramètre.

Script 49

1. @echo off

2. if "%1"=="foo" goto SecondInstance

3. set "X=bar"

4. start %~nx0 foo

5. pause

6. exit /b

7. :SecondInstance

8. echo %X%

9. pause

10. exit

Première instance :

Seconde instance :

Si l'on modifie le script 49 en ajoutant le paramètre /i dans la commande start (script 50), alors le nouveau processus sera créé en héritant du contexte original du processus hôte dans lequel la variable X n'a pas été déclarée.

Script 50

1. @echo off

2. if "%1"=="foo" goto SecondInstance

3. set "X=bar"

4. start /i %~nx0 foo

5. pause

6. exit /b

7. :SecondInstance

8. echo %X%

9. pause

10. exit

Première instance :

Seconde instance :

Une autre application intéressante de la commande start est l'exécution parallèle à l'aide du paramètre /b comme dans le script 51.

N.B. : la commande timeout n'est pas fournie en standard sur tous les systèmes Windows. Si vous ne l'avez pas, veuillez supprimer la commande timeout du script, dé-commenter les commandes echo, ping et supprimer le caractère d'échappement dans la commande ping.

Script 51

1. @echo off

2. if "%1"=="foo" goto SecondInstance

3. start /b %~nx0 foo

4. ::echo Attendez quelques instant ...

5. ::ping -n 15 localhost^>nul

6. timeout /t 10 /nobreak

7. pause

8. exit /b

9. :SecondInstance

10. echo %1

11. exit

IV-J - La commande setlocal

Cette commande permet de modifier partiellement les contextes en créant un contexte local. Les variables et leurs valeurs, dans ce nouveau contexte local, sont soumises aux règles inhérentes à la création de contexte. L'avantage de cette commande est que l'on peut créer un contexte sans passer par un appel. Ce contexte local ne possède pas d'adresse de retour ni de paramètres d'appel. Seules les variables, et éventuellement les extensions de commande, sont affectées. Ainsi, toutes les variables créées ou modifiées dans ce contexte local y sont propres. Cette modification prend fin lorsque l'exécution rencontre la commande endlocal. La commande setlocal prend aussi les paramètres :

- enabledelayedexpansion qui active l'expansion retardée ;

- disabledelayedexpansion qui désactive l'expansion retardée ;

- enableextensions qui active les extensions de commande ;

- disableextensions qui désactive les extensions de commande.

setlocal [enableextensions|disableextensions] [enabledelayedexpansion|disabledelayedexpansion]

IV-K - La commande endlocal

Cette commande termine le contexte local généré par la commande setlocal correspondante. Plusieurs contextes locaux pouvant être empilés les uns au-dessus des autres, toutes les modifications apportées dans le contexte local qui se termine sont perdues, y compris les modifications apportées par les paramètres de la commande setlocal. La commande endlocal ne prend aucun paramètre. Exemple pour setlocal et endlocal avec le script 52 :

Script 52

1. @echo off

2. setlocal

3. set "X=foo"

4. echo %X%

5. endlocal

6. echo %X%

7. pause

8. exit

IV-L - Mise en application des contextes

Script 53

1. @echo off

3. set "ScriptName=%~nx0"

4. set "NegNum=false"

5. set /a "PrefixEnable=0"

6. set /a "UpperPrefix=0"

8. setlocal

10. :ParseArgs

11. if "%~1"=="" goto Init

12. if /i "%~1"=="/?" goto Help

13. if /i "%~1"=="-?" goto Help

14. if /i "%~1"=="/h" goto Help

15. if /i "%~1"=="-h" goto Help

16. if /i "%~1"=="/help" goto Help

17. if /i "%~1"=="-help" goto Help

18. if /i "%~1"=="/hex" (

19. set "SInt32=%~2"

20. set /a "SInt32"

21. if errorlevel 1 (

22. shift /1

23. goto BadSyntax

24. )

25. shift /1

26. shift /1

27. goto ParseArgs

28. )

29. if /i "%~1"=="/p" (

30. set /a "PrefixEnable=1"

31. if %~1 EQU /P set /a "UpperPrefix=1"

32. shift /1

33. goto ParseArgs

34. )

35. :BadSyntax

36. echo.

37. rem La commande 'net helpmsg' affiche un message prédéfini dans

38. rem la langue d'installation du système d'exploitation

39. net helpmsg 87

40. echo [ %~1 ]

41. echo.

42. if not "%~0"==":BadSyntax" endlocal

43. exit /b 1

45. :Init

46. if defined SInt32 goto Exec

47. :UnknowError

48. call :BadSyntax /hex

49. call :Help

50. endlocal

51. exit /b 2

53. :Exec

54. rem Si le nombre est négatif, on utilise son complément à un et on le définit comme négatif.

55. if %SInt32% LSS 0 (

56. set "NegNum=true"

57. set /a "SInt32=~SInt32"

58. )

59. rem On utilise un algorithme de divisions successives par 16 pour obtenir des

60. rem nombres en base 16, chaque nombre obtenu forme un quartet.

61. set /a "Nibble0= SInt32 %% 16","HighOrder0= SInt32 / 16"

62. set /a "Nibble1=HighOrder0 %% 16","HighOrder1=HighOrder0 / 16"

63. set /a "Nibble2=HighOrder1 %% 16","HighOrder2=HighOrder1 / 16"

64. set /a "Nibble3=HighOrder2 %% 16","HighOrder3=HighOrder2 / 16"

65. set /a "Nibble4=HighOrder3 %% 16","HighOrder4=HighOrder3 / 16"

66. set /a "Nibble5=HighOrder4 %% 16","HighOrder5=HighOrder4 / 16"

67. set /a "Nibble6=HighOrder5 %% 16"

68. set /a "Nibble7=HighOrder5 / 16"

69. rem Si c'est un nombre négatif^ ("%NegNum%"=="true"^),

70. rem     on retranche chaque valeur à 15 pour récupérer le nombre original.

71. if "%NegNum%"=="true" (

72. set /a "Nibble0=15  Nibble0"

73. set /a "Nibble1=15  Nibble1"

74. set /a "Nibble2=15  Nibble2"

75. set /a "Nibble3=15  Nibble3"

76. set /a "Nibble4=15  Nibble4"

77. set /a "Nibble5=15  Nibble5"

78. set /a "Nibble6=15  Nibble6"

79. set /a "Nibble7=15  Nibble7"

80. )

81. rem On transforme chaque quartet en notation hexadécimale.

82. if %Nibble0% GTR 9 (

83. if %Nibble0% EQU 10 set "Nibble0=A"

84. if %Nibble0% EQU 11 set "Nibble0=B"

85. if %Nibble0% EQU 12 set "Nibble0=C"

86. if %Nibble0% EQU 13 set "Nibble0=D"

87. if %Nibble0% EQU 14 set "Nibble0=E"

88. if %Nibble0% EQU 15 set "Nibble0=F"

89. )

90. if %Nibble1% GTR 9 (

91. if %Nibble1% EQU 10 set "Nibble1=A"

92. if %Nibble1% EQU 11 set "Nibble1=B"

93. if %Nibble1% EQU 12 set "Nibble1=C"

94. if %Nibble1% EQU 13 set "Nibble1=D"

95. if %Nibble1% EQU 14 set "Nibble1=E"

96. if %Nibble1% EQU 15 set "Nibble1=F"

97. )

98. if %Nibble2% GTR 9 (

99. if %Nibble2% EQU 10 set "Nibble2=A"

100. if %Nibble2% EQU 11 set "Nibble2=B"

101. if %Nibble2% EQU 12 set "Nibble2=C"

102. if %Nibble2% EQU 13 set "Nibble2=D"

103. if %Nibble2% EQU 14 set "Nibble2=E"

104. if %Nibble2% EQU 15 set "Nibble2=F"

105. )

106. if %Nibble3% GTR 9 (

107. if %Nibble3% EQU 10 set "Nibble3=A"

108. if %Nibble3% EQU 11 set "Nibble3=B"

109. if %Nibble3% EQU 12 set "Nibble3=C"

110. if %Nibble3% EQU 13 set "Nibble3=D"

111. if %Nibble3% EQU 14 set "Nibble3=E"

112. if %Nibble3% EQU 15 set "Nibble3=F"

113. )

114. if %Nibble4% GTR 9 (

115. if %Nibble4% EQU 10 set "Nibble4=A"

116. if %Nibble4% EQU 11 set "Nibble4=B"

117. if %Nibble4% EQU 12 set "Nibble4=C"

118. if %Nibble4% EQU 13 set "Nibble4=D"

119. if %Nibble4% EQU 14 set "Nibble4=E"

120. if %Nibble4% EQU 15 set "Nibble4=F"

121. )

122. if %Nibble5% GTR 9 (

123. if %Nibble5% EQU 10 set "Nibble5=A"

124. if %Nibble5% EQU 11 set "Nibble5=B"

125. if %Nibble5% EQU 12 set "Nibble5=C"

126. if %Nibble5% EQU 13 set "Nibble5=D"

127. if %Nibble5% EQU 14 set "Nibble5=E"

128. if %Nibble5% EQU 15 set "Nibble5=F"

129. )

130. if %Nibble6% GTR 9 (

131. if %Nibble6% EQU 10 set "Nibble6=A"

132. if %Nibble6% EQU 11 set "Nibble6=B"

133. if %Nibble6% EQU 12 set "Nibble6=C"

134. if %Nibble6% EQU 13 set "Nibble6=D"

135. if %Nibble6% EQU 14 set "Nibble6=E"

136. if %Nibble6% EQU 15 set "Nibble6=F"

137. )

138. if %Nibble7% GTR 9 (

139. if %Nibble7% EQU 10 set "Nibble7=A"

140. if %Nibble7% EQU 11 set "Nibble7=B"

141. if %Nibble7% EQU 12 set "Nibble7=C"

142. if %Nibble7% EQU 13 set "Nibble7=D"

143. if %Nibble7% EQU 14 set "Nibble7=E"

144. if %Nibble7% EQU 15 set "Nibble7=F"

145. )

146. rem Les quartets 0 à 3 forment le Least Significant Word.

147. set "LSW=%Nibble3%%Nibble2%%Nibble1%%Nibble0%"

148. rem Les quartets 4 à 7 forment le Most Significant Word.

149. set "MSW=%Nibble7%%Nibble6%%Nibble5%%Nibble4%"

150. rem On affiche le résultat.

151. if %PrefixEnable% EQU 1 (

152. if %UpperPrefix% EQU 1 (

153. echo 0X%MSW%%LSW%

154. ) else (

155. echo 0x%MSW%%LSW%

156. )

157. ) else (

158. echo %MSW%%LSW%

159. )

160. goto End

163. :Help

164. echo.

165. echo %ScriptName% [/p^|/P] /hex ^<number^>

166. echo %ScriptName% {/?^|-?^|/h^|-h^|/help^|-help}

167. echo.

168. echo     /hex    Définit le ^<number^> qui doit être exprimé en hexadécimal.

169. echo     /p      Définit que le préfixe doit être affiché en minuscules.

170. echo     /P      Définit que le préfixe doit être affiché en majuscules.

171. echo.    /?      Affiche cette aide.

172. echo.

174. :End

175. if not "%~0"==":Help" endlocal

176. exit /b 0

Le script 53 doit être appelé avec des paramètres afin de fonctionner. Ainsi, quand on appelle ce script avec l'un des paramètres suivants : /?, -?, /h, -h, /help ou -help, l'aide est affichée. Si on appelle le script avec les paramètres /hex n (ou n est le nombre voulu), il affiche la représentation hexadécimale de ce nombre. Notez également l'utilisation du paramètre %~nx0 pour définir le nom du script, ici, la fonction :Help est appelée via un call au label :UnknowError (la commande exit /b 0 du label :End fournit le saut à l'adresse de retour), %0 aurait été, alors, la chaîne :Help.

Un script devrait toujours avoir un squelette similaire à celui du script 53. Pour en connaître la raison, examinons ses différentes parties :

- le script commence par @echo off pour rendre plus net l'affichage, puis les variables de configuration sont initialisées avec leurs valeurs par défaut, ce qui permet au script de fonctionner même si ces valeurs ne sont pas modifiées par la suite. La commande setlocal est utilisée pour les cas d'erreur, par exemple si le nombre donné via l'argument /hex dépasse 32 bits. La variable ErrorLevel n'est pas remise à jour suite à une erreur dans une commande interne, ainsi, si l'on appelle le script depuis l'interpréteur avec en paramètre un nombre invalide, tous les appels du script qui suivront, même avec un nombre valide, se termineront sur une erreur. Pensez à utiliser la commande endlocal avant chaque point de sortie du script ;

- vient ensuite le label :ParseArgs dans lequel les paramètres d'appel vont être vérifiés, chaque fois qu'un paramètre est trouvé, sa valeur est définie dans la variable correspondante puis les paramètres d'appel sont décalés avant le retour au label :ParseArgs. Si le paramètre fourni dans la commande d'appel n'est pas trouvé, l'exécution continue jusqu'au label :BadSyntax qui affiche un message d'erreur et quitte le script avec le code d'erreur 1 ;

- une fois tous les paramètres lus, l'exécution est redirigée vers le label :Init, qui a pour fonction de vérifier que les données de travail ont bien été fournies dans la commande d'appel. En effet, ce script appelé sans paramètre s'exécute au moins jusqu'au label :Init. Dans ce label, il convient, en général, de modifier les variables de configuration en fonction des valeurs fournies dans la commande d'appel. Si les données fournies ne sont pas valides, l'exécution continue au label :UnknowError qui, dans notre cas, va afficher où se trouve l'erreur ainsi que l'aide ;

- si les données de travail sont valides, l'exécution est redirigée vers le label :Exec qui va exécuter le travail requis puis afficher le résultat avant d'être redirigée vers le label :End ;

- le label  :Help fournit une aide en ligne de commande (c'est toujours utile). Celui-ci se trouvant juste devant le label :End, il peut à la fois être utilisé comme une fonction ou comme une portion de code classique. Notez l'échappement de la commande echo avec le point à la ligne echo. /? Affiche cette aide. sans quoi l'interpréteur aurait affiché l'aide de la commande echo.

V - Les boucles

Les boucles sont gérées par la commande for, elle permettent une grande quantité d'actions sur des fichiers, des répertoires ou des chaînes de caractères. La boucle for se compose d'un ensemble sur lequel s'opère une commande. L'<ensemble> est parsé puis transite au moyen d'une <variable> vers la <commande>. La syntaxe de base de la boucle for est la suivante.

for %<variable> in (<ensemble>) do <commande>

La <variable> est en fait un paramètre de commande propre à la boucle for. Elle utilise donc la syntaxe et les modificateurs de paramètre de commande (voir section IV.E.1 pour plus d'information), à ceci près que le paramètre est désigné par une lettre et que le caractère % du paramètre doit être échappé lorsque la boucle est utilisée dans un script. Ainsi dans un script, le paramètre %A doit être utilisé avec le caractère d'échappement %, ce qui nous donne la syntaxe %%A. Il faut aussi noter que les paramètres de la boucle for respectent la casse, ainsi %a est différent de %A.

L'<ensemble> est composé d'une ou plusieurs entrées. Si plusieurs entrées sont présentes, elles doivent être séparées par des virgules pour permettre à la boucle de les distinguer correctement. Cet <ensemble> peut être :

- soit des noms de fichiers (avec leurs chemins d'accès s'ils ne sont pas dans le répertoire courant) ;

- soit des chemins d'accès ;

- soit des chaînes de caractères.

Toutes les composantes de l'<ensemble> sont passées à la <commande> sous forme de chaînes de caractères. Seule l'utilisation qui en est faite dans le traitement détermine s'il s'agit d'une chaîne de caractères, d'un nom de fichier ou d'un chemin d'accès. Si une chaîne de l'<ensemble> contient un ou plusieurs espaces, elle doit être placée entre guillemets. Exemple avec le script 54 :

Script 54

1. @echo off

2. for %%A in (texte) do echo %%A

3. for %%B in ("%cd%") do if exist %%B echo %%B

4. pause

5. exit /b

Si les extensions de commande sont activées, la boucle for peut prendre un des paramètres de la liste suivante :

- /d ;

- /r ;

- /l ;

- /f.

V-A - Les boucles pour chemin d'accès

Les boucles pour chemin d'accès permettent d'effectuer des recherches de dossiers ou de fichiers. La recherche de dossier s'effectue avec le paramètre /d tandis que la recherche de fichier s'effectue avec le paramètre /r. Leurs syntaxes sont les suivantes :

for /d %<paramètre> in (["][<chemin_d'accès>\]{*|<motif>}["]) do <cmd>

for /r ["][<chemin_d'accès>\]["] %<paramètre> in (["]{*|<motif>|.}["]) do <cmd>

Options

Descriptions

<chemin_d'accès>

Chemin d'accès de base de la recherche. Si aucun chemin n'est fourni dans la boucle, alors la recherche se porte sur le répertoire courant.

*

Caractère générique. Remplace plusieurs caractères.

?

Caractère générique. Remplace un seul caractère.

<motif>

Le motif définit le nom du fichier qu'il faut chercher, il peut être composé de tout caractère, y compris les caractères génériques.

.

Avec le paramètre /r, le point, s'il est utilisé seul, signifie que la recherche porte sur les noms de dossier.

Tableau 12 : Paramètres des boucles pour chemins d'accès

Exemple avec le script 55.

Script 55

1. @echo off

2. mkdir "%cd%\foo"

3. mkdir "%cd%\foo\bar"

4. mkdir "%cd%\foo\beer"

5. call :WriteFile >>"%cd%\foo\bar\bar.bat"

6. call :WriteFile >>"%cd%\foo\bar\bar.txt"

7. call :WriteFile >>"%cd%\foo\beer\beer.bat"

8. call :WriteFile >>"%cd%\foo\beer\beer.txt"

9. echo Liste des dossiers:

10. for /d %%A in ("%cd%\foo\*") do echo %%A

11. echo.

12. echo Liste des scripts batch:

13. for /r "%cd%\foo" %%B in ("*.bat") do echo %%~B

14. echo.

15. pause

16. rd /s /q "%cd%\foo"

17. exit /b

18. :WriteFile

19. echo @echo off

20. echo pause

21. goto :eof

V-B - Les boucles pour compteurs

Le paramètre /l permet d'effectuer une boucle avec comme paramètre un compteur numérique, ainsi l'<ensemble> prend la forme : <origine>, <pas_incrémentiel>, <fin>. La boucle commencera le traitement avec comme paramètre un nombre ayant pour valeur <origine> puis, après chaque itération de la boucle, sera ajouté <pas_incrémentiel> tant que <fin> n'est pas atteinte. Lorsque <fin> est atteinte la boucle exécute sa dernière itération. <origine>, <pas_incrémentiel> et <fin> peuvent être séparés, soit par des espaces, soit par des virgules.

for /l %<paramètre> in (<origine> <pas_incrémentiel> <fin>) do <cmd>

for /l %<paramètre> in (<origine>,<pas_incrémentiel>,<fin>) do <cmd>

Script 56

1. @echo off

2. for /l %%A in (1 1 20) do echo Le paramètre A a pour valeur %%A.

3. pause

4. exit /b

Comme les nombres dans l'interpréteur ont une définition de 32 bits et utilisent tous l'arithmétique signée, les limites de la boucle for /l sont celles des nombres entiers signés. Ainsi l'<origine>, le <pas_incrémentiel> et la <fin> peuvent tous être compris entre -2147483648 et +2147483647. Le script 57 fonctionne donc parfaitement, mais, comme vous pouvez l'imaginer, son exécution est très longue.

Script 57

1. @echo off

2. for /l %%A in (2147483647 -1 -2147483648) do echo Le paramètre A a pour valeur %%A.

3. pause

4. exit /b

V-C - Les boucles de recherche

Le paramètre /f indique à la boucle for que le traitement peut s'opérer sur des chaînes de caractères, des fichiers ou des commandes, mais à des fins de recherche. Ce paramètre a la particularité de prendre des options supplémentaires permettant de parser l'<ensemble>.

for /f ["<options>"] %<paramètre> in (<ensemble>) do <commande>

Les options prises en charge par for /f sont les suivantes :

Options

Descriptions

eol=c

Définit le préfixe de commentaire, qui contrairement à ce que suggère son nom (eol : « End Of Line », ou fin de ligne) doit se trouver en début de ligne. Tout ce qui se trouve après le caractère spécifié sera ignoré par la boucle for. Il est recommandé d'utiliser le plus souvent possible le caractère ; pour la compatibilité avec les fichiers *.ini et *.inf (sous Windows), ou le caractère # si le script est susceptible de traiter des fichiers de type Unix.

skip=n

Nombre de lignes à ignorer au début de l'ensemble.

delims=xxx

Délimiteurs, par défaut : l'espace et la tabulation. Cette option doit toujours être donnée en dernière position afin de pouvoir spécifier un retour à la ligne comme délimiteur. Cela se fait en écrivant l'option delims avec le symbole égal directement suivi du guillemet fermant du bloc d'options.

tokens=x,y-z*

Spécifie l'index des jetons devant être transmis au corps de la boucle for. Chaque jeton étant une chaîne se trouvant entre deux délimiteurs définis par l'option delims. La forme y-z définit une étendue allant des jetons y à z. Si le dernier caractère de l'option tokens est un astérisque (*), alors une variable supplémentaire est allouée et recevra tout le texte qui se trouve après le dernier jeton (y compris les délimiteurs qui pourraient si trouver). Chaque jeton sera attribué à un paramètre de la boucle for en partant de celui spécifié dans la boucle et dans l'ordre alphabétique. Si le paramètre %A est spécifié avec l'option tokens=1-3*, alors le premier jeton sera accessible dans le corps de la boucle via le paramètre %A, le second via le paramètre %B, le troisième via le paramètre %C et enfin le reste de la ligne via le paramètre %D.

usebackq

Spécifie que la nouvelle sémantique est en place, chaque chaîne se trouvant entre guillemets simples inversés (`) est exécutée en tant que commande et une chaîne entre guillemets simples (') est une chaîne de caractères. La nouvelle sémantique permet ainsi l'utilisation de guillemets doubles (") pour citer des noms de fichiers ou pour utiliser une chaîne qui contient des guillemets doubles (").

Tableau 13 : Paramètres de la boucle « for /f »

Script 58

1. @echo off

2. setlocal enabledelayedexpansion

3. :: La chaîne « 1^>^>"%cd%\test.txt" » est une redirection et sera expliquée au chapitre VII.

4. 1>"%cd%\test.txt" echo Ligne 1

5. 1>>"%cd%\test.txt" echo Ligne 2

6. 1>>"%cd%\test.txt" echo Ligne 3

7. 1>>"%cd%\test.txt" echo Ligne 4

8. 1>>"%cd%\test.txt" echo Ligne 5

9. 1>>"%cd%\test.txt" echo ;un petit commentaire

11. for /f "eol=; delims=" %%A in ('type test.txt') do echo %%A

12. echo.

13. for /f "eol=; skip=4 delims=" %%A in ('type test.txt') do echo %%A

14. del /Q test.txt

15. echo.

16. for /f "tokens=1-5*" %%A in ("1 2 3 4 5 6 7 8 9") do (

17. echo %%A

18. echo %%B

19. echo %%C

20. echo %%D

21. echo %%E

22. echo %%F

23. )

24. echo.

25. for /f "usebackq delims=" %%A in (`type "%cd%\%~nx0"`) do (

26. echo %%A

27. )

28. echo.

29. pause

30. exit /b

Plusieurs points sont à noter dans le script 58 :

- le premier est l'utilisation de l'option eol=; qui supprime la chaîne ;un petit commentaire des résultats obtenus des deux premières boucles ;

- le second est l'utilisation de l'option skip=4 qui supprime les quatre premières lignes du résultat de la seconde boucle ;

- le troisième est l'absence de l'option delims dans la troisième boucle, en effet l'espace et la tabulation sont les délimiteurs par défaut ;

- le dernier est l'utilisation de l'option usebackq et des caractères ` (guillemet simple inversé) qui permet l'utilisation de guillemets doubles pour le chemin d'accès.

V-D - Les échappements propres à la boucle for

La boucle for possède un certain nombre de caractères significatifs tel que " ' ` ,. Si les guillemets et autres apostrophes peuvent être gérés avec un bon jeu d'options dans la boucle, il n'en est pas de même avec la virgule. Ainsi, considérons le script suivant qui devrait permettre de trouver les volumes qui sont des lecteurs de CD/DVD.

Script 59

1. @echo off

2. for /f "usebackq skip=1 tokens=1,2" %%A in (`wmic volume where "DriveType=\"5\"" get DriveLetter,Capacity`) do (

3. echo %%A %%B

4. )

5. pause

6. exit /b 0

Ce script se finit sur une erreur, car les guillemets simples inversés modifient la façon dont la chaîne de l'ensemble est prise en compte et de ce fait, la virgule provoque l'erreur. Il faut donc échapper la virgule avec le caractère ^ pour qu'elle soit incluse dans la chaîne sans être traitée par la boucle comme le montre le script 60.

Script 60

1. @echo off

2. for /f "usebackq skip=1 tokens=1,2" %%A in (`wmic volume where "DriveType=\"5\"" get DriveLetter^,Capacity`) do (

3. echo %%A %%B

4. )

5. pause

6. exit /b 0

L'<ensemble>, qu'il soit une commande ou autre chose, est toujours traité dans un premier temps comme une chaîne. Ainsi les opérateurs de redirection (voir chapitre VII) doivent aussi être échappés.

Script 61

1. @echo off

2. 1>test.txt echo foo

3. 1>>test.txt echo bar

4. 1>>test.txt echo foobar

5. for /f "usebackq delims=" %%A in (`type test.txt ^| find "foo"`) do (

6. echo %%A

7. )

8. del /q test.txt

9. pause

10. exit /b 0

V-E - Mise en application des boucles

Script 62

1. @echo off

3. set "ScriptName=%~nx0"

4. set "NegNum=false"

5. set /a "PrefixEnable=0"

6. set /a "UpperPrefix=0"

8. setlocal

10. :ParseArgs

11. if "%~1"=="" goto Init

12. if /i "%~1"=="/?" goto Help

13. if /i "%~1"=="-?" goto Help

14. if /i "%~1"=="/h" goto Help

15. if /i "%~1"=="-h" goto Help

16. if /i "%~1"=="/help" goto Help

17. if /i "%~1"=="-help" goto Help

18. if /i "%~1"=="/hex" (

19. set "Nibble7=%~2"

20. set /a "Nibble7"

21. if errorlevel 1 (

22. shift /1

23. goto BadSyntax

24. )

25. shift /1

26. shift /1

27. goto ParseArgs

28. )

29. if /i "%~1"=="/p" (

30. set /a "PrefixEnable=1"

31. if %~1 EQU /P set /a "UpperPrefix=1"

32. shift /1

33. goto ParseArgs

34. )

35. :BadSyntax

36. echo.

37. for /f "delims=" %%A in ('net helpmsg 87') do echo [ %~1 ] %%A

38. echo.

39. if not "%~0"==":BadSyntax" endlocal

40. exit /b 1

42. :Init

43. if defined Nibble7 goto Exec

44. :UnknowError

45. call :BadSyntax /hex

46. call :Help

47. endlocal

48. exit /b 2

50. :Exec

51. rem Si le nombre est négatif, on utilise son complément à un et on le définit comme négatif.

52. if %Nibble7% LSS 0 (

53. set /a "Nibble7=~Nibble7"

54. set "NegNum=true"

55. )

56. rem Divisions successives par 16

57. for /l %%A in (0 1 6) do set /a "Nibble%%A=Nibble7 %% 16", "Nibble7/=16"

58. rem Si le nombre est négatif, la valeur de chaque chiffre ^(Nibble^) est retranchée à 15.

59. if /i "%NegNum%"=="true" for /l %%A in (0 1 7) do set /a "Nibble%%A=15 - Nibble%%A"

60. rem Chaque chiffre ayant une valeur supérieure à 9 est transformé en sa représentation hexadécimale.

61. if %Nibble0% GTR 9 (

62. if %Nibble0% EQU 10 set "Nibble0=A"

63. if %Nibble0% EQU 11 set "Nibble0=B"

64. if %Nibble0% EQU 12 set "Nibble0=C"

65. if %Nibble0% EQU 13 set "Nibble0=D"

66. if %Nibble0% EQU 14 set "Nibble0=E"

67. if %Nibble0% EQU 15 set "Nibble0=F"

68. )

69. if %Nibble1% GTR 9 (

70. if %Nibble1% EQU 10 set "Nibble1=A"

71. if %Nibble1% EQU 11 set "Nibble1=B"

72. if %Nibble1% EQU 12 set "Nibble1=C"

73. if %Nibble1% EQU 13 set "Nibble1=D"

74. if %Nibble1% EQU 14 set "Nibble1=E"

75. if %Nibble1% EQU 15 set "Nibble1=F"

76. )

77. if %Nibble2% GTR 9 (

78. if %Nibble2% EQU 10 set "Nibble2=A"

79. if %Nibble2% EQU 11 set "Nibble2=B"

80. if %Nibble2% EQU 12 set "Nibble2=C"

81. if %Nibble2% EQU 13 set "Nibble2=D"

82. if %Nibble2% EQU 14 set "Nibble2=E"

83. if %Nibble2% EQU 15 set "Nibble2=F"

84. )

85. if %Nibble3% GTR 9 (

86. if %Nibble3% EQU 10 set "Nibble3=A"

87. if %Nibble3% EQU 11 set "Nibble3=B"

88. if %Nibble3% EQU 12 set "Nibble3=C"

89. if %Nibble3% EQU 13 set "Nibble3=D"

90. if %Nibble3% EQU 14 set "Nibble3=E"

91. if %Nibble3% EQU 15 set "Nibble3=F"

92. )

93. if %Nibble4% GTR 9 (

94. if %Nibble4% EQU 10 set "Nibble4=A"

95. if %Nibble4% EQU 11 set "Nibble4=B"

96. if %Nibble4% EQU 12 set "Nibble4=C"

97. if %Nibble4% EQU 13 set "Nibble4=D"

98. if %Nibble4% EQU 14 set "Nibble4=E"

99. if %Nibble4% EQU 15 set "Nibble4=F"

100. )

101. if %Nibble5% GTR 9 (

102. if %Nibble5% EQU 10 set "Nibble5=A"

103. if %Nibble5% EQU 11 set "Nibble5=B"

104. if %Nibble5% EQU 12 set "Nibble5=C"

105. if %Nibble5% EQU 13 set "Nibble5=D"

106. if %Nibble5% EQU 14 set "Nibble5=E"

107. if %Nibble5% EQU 15 set "Nibble5=F"

108. )

109. if %Nibble6% GTR 9 (

110. if %Nibble6% EQU 10 set "Nibble6=A"

111. if %Nibble6% EQU 11 set "Nibble6=B"

112. if %Nibble6% EQU 12 set "Nibble6=C"

113. if %Nibble6% EQU 13 set "Nibble6=D"

114. if %Nibble6% EQU 14 set "Nibble6=E"

115. if %Nibble6% EQU 15 set "Nibble6=F"

116. )

117. if %Nibble7% GTR 9 (

118. if %Nibble7% EQU 10 set "Nibble7=A"

119. if %Nibble7% EQU 11 set "Nibble7=B"

120. if %Nibble7% EQU 12 set "Nibble7=C"

121. if %Nibble7% EQU 13 set "Nibble7=D"

122. if %Nibble7% EQU 14 set "Nibble7=E"

123. if %Nibble7% EQU 15 set "Nibble7=F"

124. )

125. if %PrefixEnable% EQU 1 (

126. if %UpperPrefix% EQU 1 (

127. echo 0X%Nibble7%%Nibble6%%Nibble5%%Nibble4%%Nibble3%%Nibble2%%Nibble1%%Nibble0%

128. ) else (

129. echo 0x%Nibble7%%Nibble6%%Nibble5%%Nibble4%%Nibble3%%Nibble2%%Nibble1%%Nibble0%

130. )

131. ) else (

132. echo %Nibble7%%Nibble6%%Nibble5%%Nibble4%%Nibble3%%Nibble2%%Nibble1%%Nibble0%

133. )

134. goto End

137. :Help

138. echo.

139. echo %ScriptName% [/p^|/P] /hex ^<number^>

140. echo %ScriptName% {/?^|-?^|/h^|-h^|/help^|-help}

141. echo.

142. echo     /hex    Définit le ^<number^> qui doit être exprimé en hexadécimal.

143. echo     /p      Définit que le préfixe doit être affiché en minuscules.

144. echo     /P      Définit que le préfixe doit être affiché en majuscules.

145. echo.    /?      Affiche cette aide.

146. echo.

148. :End

149. if not "%~0"==":Help" endlocal

150. exit /b 0

Comme vous pouvez le constater, le script a vu sa taille réduite, les plus malins auront sûrement compris que la transformation en représentation hexadécimale peut se faire via une boucle, mais nous verrons cela au prochain chapitre. Les boucles auront, quand même, permis de condenser les opérations répétitives. La variable SInt32 a été remplacée par Nibble7 afin de traiter les divisions successives via une boucle for sans avoir à rajouter une ligne pour traiter le dernier quartet. Le script 62 peut facilement être utilisé dans un autre script grâce à la boucle for /f, comme avec le script 63 (remplacer <scriptname> par le nom donné au script 62).

Script 63

1. @echo off

2. for /f "delims=" %%A in ('<scriptname> /p /hex -1') do set "Result=%%A"

3. echo Résultat:    %Result%

4. echo.

5. pause

6. exit /b

Dans le script 63, l'option "delims=" est utilisée pour récupérer la sortie de la commande echo 0x%Nibble7%%Nibble6%… du script 62, car toutes les commandes echo se finissent par un retour à la ligne, le délimiteur doit donc être un retour à la ligne. Si plusieurs lignes doivent être récupérées en sortie d'un script, il est possible d'ajouter un compteur de ligne, comme dans le script 64.

Script 64

1. @echo off

2. setlocal enabledelayedexpansion

4. rem Dans ce script, l'expansion retardée des variables est utilisée, elle sera cependant abordée au chapitre VI.

6. 1>"%cd%\test.bat" echo @echo off

7. 1>>"%cd%\test.bat" echo echo Ligne 1

8. 1>>"%cd%\test.bat" echo echo Ligne 2

9. 1>>"%cd%\test.bat" echo echo Ligne 3

10. set /a "Counter=1"

11. for /f "delims=" %%A in ('test.bat') do (

12. set "Result!Counter!=%%A"

13. set /a "Counter+=1"

14. )

15. del /Q test.bat

16. for /l %%B in (1 1 !Counter!) do (

17. if not "!Result%%B!"=="" echo Résultat %%B:    !Result%%B!

18. )

19. pause

20. exit /b

VI - L'expansion retardée des variables

Par défaut, l'expansion retardée des variables n'est pas activée, cependant il est possible de modifier ce comportement grâce à deux clés de registre contenant des valeurs REG_DWORD, à 0x1 si l'expansion retardée est activée et à 0x0 si l'expansion retardée est désactivée. Les entrées spécifiées dans la clé de l'utilisateur prennent le pas sur les entrées spécifiées dans la clé machine. Si l'expansion retardée est activée ou désactivée via les paramètres /v:on ou /v:off de la commande cmd, alors cette nouvelle configuration prend le pas sur la configuration du registre. Si par la suite, la commande setlocal est utilisée pour activer ou désactiver l'expansion retardée, alors les paramètres de la commande setlocal ont priorité sur les paramètres /v:on et /v:off.

Clés registres relatives à l'expansion retardée des variables

HKEY_LOCAL_MACHINE\Software\Microsoft\Command Processor\DelayedExpansion
HKEY_CURRENT_USER\Software\Microsoft\Command Processor\DelayedExpansion

Les variables ont une portée limitée au contexte dans lequel elles sont définies. Pour pouvoir utiliser des variables définies dans un contexte descendant, il faut utiliser l'expansion retardée. Celle-ci peut-être activée soit par défaut si les clés de registre appropriées ont été modifiées dans ce sens, ou alors avec la commande setlocal enabledelayedexpansion. Une fois que l'expansion retardée est activée, chaque variable définie sera accessible aussi bien dans la pile que dans le tas.

Ainsi, lorsque vous expanserez une variable dans un contexte ascendant, la variable prendra la dernière valeur qui lui a été attribuée comme spécifié au chapitre IV : si l'expansion retardée est activée, à chaque création ou modification d'une variable, sa valeur est copiée à la fois dans le tas et sur la pile. L'expansion de ces variables se fait en utilisant le caractère ! (au lieu du caractère %) afin d'indiquer à l'interpréteur leurs emplacements.

Toutes les variables ayant été déclarées avant l'activation de l'expansion retardée ne sont accessibles que dans le contexte qui leur est propre ou dans un contexte descendant du contexte de création/modification.

VI-A - Cas de la boucle for

Dans une boucle for, la dernière exécution du corps de for est « fixée » dans le contexte d'appel de la boucle. Ainsi, dans le script 65, seule la dernière valeur est ajoutée à la variable Var.

Script 65

1. @echo off

2. for %%A in (a,z) do (

3. set "Var=%Var% %%A"

4. )

5. echo %Var%

6. pause

7. exit /b 0

Si l'on modifie le script 65 en ajoutant l'expansion retardée, l'opération s'effectue sans erreur (script 66).

Script 66

1. @echo off

2. setlocal enabledelayedexpansion

3. for %%A in (a,z) do (

4. set "Var=!Var! %%A"

5. )

6. echo %Var%

7. pause

8. exit /b 0

VI-B - Générer dynamiquement des noms de variables

Une autre application intéressante de l'expansion retardée des variables est de pouvoir créer des noms de variables en fonction de paramètres tel qu'un index numérique ou une chaîne de caractères. Des noms de variables ainsi construits permettent de créer une abstraction de tableau qu'il sera par la suite possible de parcourir rapidement avec une boucle for. Du fait de la syntaxe des paramètres de la boucle for, si l'on venait à placer le paramètre variable (l'index numérique ou la chaîne de caractères) entre des caractères %, le parsage de la ligne de commande générerait forcément une erreur. Par exemple avec un paramètre %%A ayant une valeur de 1, si l'on veut obtenir la variable ayant le nom Index1, il faudrait l'écrire %Index%%A%,mais l'interpréteur comprendrait qu'il y a une variable %Index% et une variable %A%. Si, avec le même exemple, on utilise l'expansion retardée, la variable s'écrirait !Index%%A!, ainsi plus d'erreur de parsage possible.

Script 67

1. @echo off

2. setlocal enabledelayedexpansion

3. 1>>test.txt echo Ligne1

4. 1>>test.txt echo Ligne2

5. 1>>test.txt echo Ligne3

6. set /a "Counter=1"

7. for /f "delims=" %%A in ('type test.txt') do (

8. set "Var!Counter!=%%A"

9. set /a "Counter+=1"

10. )

11. del /Q test.txt

12. for /l %%B in (1 1 !Counter!) do if not "!Var%%B!"=="" echo !Var%%B!

13. pause

14. exit /b 0

Afin de rendre plus intuitive la lecture du script, il est possible de prendre des habitudes syntaxiques pour représenter de telles abstractions de tableau. Les caractères qui viennent tout de suite à l'esprit sont sûrement les caractères [ et ], ainsi une entrée de tableau pourrait s'écrire NomTableau[i] ou i est l'index numérique de position dans le tableau. Il suffit alors de parcourir le tableau indexé avec une boucle for /l ou de modifier directement la variable contenant la valeur à l'index voulu.

Les abstractions de tableaux littéraux peuvent être créées sur le même principe, ainsi une entrée de tableau serait accessible via l'identifiant NomTableau[NomEntrée]. Le parcours d'un tel tableau peut se faire avec une boucle for basique comme dans le script 68.

Script 68

1. @echo off

2. setlocal enabledelayedexpansion

4. set "Array[One]=Entrée 1"

5. set "Array[Two]=Entrée 2"

6. set "Array[Three]=Entrée 3"

8. for %%A in (One,Two,Three) do echo !Array[%%A]!

10. pause

11. exit /b 0

Il faut savoir que l'interpréteur, par défaut, n'autorise que 8192 octets de données pour l'ensemble des valeurs des variables, mais autorise 64Ko de données pour l'ensemble des définitions de variables. Chaque définition de variable est composée du nom de la variable, du signe égal et de la valeur de la variable. Il reste donc 57344 octets (56Ko) pour placer les noms de variables et le signe égal, ce qui permet d'utiliser les noms de variables comme valeur significative et de récréer des structures de données ou des abstractions de tableaux.

VI-C - Parcourir une chaîne

L'expansion retardée permet une quantité d'actions sur les variables en modifiant l'évaluation syntaxique par l'interpréteur, la seule limite est la syntaxe. Par exemple, il est possible de vouloir connaître la taille d'une chaîne de caractères lorsqu'un script formate son affichage (ASCII Art, mini jeu en batch, etc.). Exemple avec le script 69.

Script 69

1. @echo off

2. setlocal enabledelayedexpansion

3. set "X=xyz"

4. set "Y="

5. echo X:

6. call :strlen "%X%"

7. call :print "%X%" %ErrorLevel%

8. echo Y:

9. call :strlen "%Y%"

10. call :print "%Y%" %ErrorLevel%

11. echo.

12. pause

13. exit /b 0

14. :strlen

15. set "str=%~1"

16. for /l %%A in (0 1 4096) do (

17. if "!str:~%%A,1!"=="" exit /b %%A

18. )

19. exit /b 0

20. :print

21. if %~2 GEQ 1 (

22. echo La chaîne '%~1' a une taille de %~2 caractère^(s^).

23. ) else (

24. echo La chaîne est vide.

25. )

26. goto :eof

VI-D - Mise en application de l'expansion retardée

Vous reconnaissez notre script témoin, celui-ci a encore été modifié pour le rendre plus efficient. La transformation en notation hexadécimale reprend le concept de génération dynamique de nom de variable. Dans le cas des divisions successives, l'utilisation de l'expansion retardée n'est pas nécessaire, car la commande set le gère automatiquement. De même, lors de l'expansion des variables NibbleX dans la commande echo, celles-ci n'ayant jamais été déclarées dans le contexte courant et l'expansion retardée étant activée depuis le début du script, elles sont donc implicitement adressées dans le tas. Nibble7 a été déclarée dans le contexte du script, cependant c'est toujours la dernière qui est traitée dans les boucles, donc ses valeurs successives sont toujours fixées dans le contexte du script.

Script 70

1. @echo off

3. set "ScriptName=%~nx0"

4. set "NegNum=false"

5. set /a "PrefixEnable=0"

6. set /a "UpperPrefix=0"

8. :: La commande setlocal utilisée avec des paramètres ne créée pas de contexte local, mais modifie simplement celui-ci, d'où la double utilisation de setlocal.

9. setlocal

10. setlocal enabledelayedexpansion

12. :ParseArgs

13. if "%~1"=="" goto Init

14. if /i "%~1"=="/?" goto Help

15. if /i "%~1"=="-?" goto Help

16. if /i "%~1"=="/h" goto Help

17. if /i "%~1"=="-h" goto Help

18. if /i "%~1"=="/help" goto Help

19. if /i "%~1"=="-help" goto Help

20. if /i "%~1"=="/hex" (

21. set "Nibble7=%~2"

22. set /a "Nibble7"

23. if errorlevel 1 (

24. shift /1

25. goto BadSyntax

26. )

27. shift /1

28. shift /1

29. goto ParseArgs

30. )

31. if /i "%~1"=="/p" (

32. set /a "PrefixEnable=1"

33. if %~1 EQU /P set /a "UpperPrefix=1"

34. shift /1

35. goto ParseArgs

36. )

37. :BadSyntax

38. echo.

39. for /f "delims=" %%A in ('net helpmsg 87') do echo [ %~1 ] %%A

40. echo.

41. if not "%~0"==":BadSyntax" endlocal

42. exit /b 1

44. :Init

45. if defined Nibble7 goto Exec

46. :UnknowError

47. call :BadSyntax /hex

48. call :Help

49. endlocal

50. exit /b 2

52. :Exec

53. rem Si le nombre est négatif, on utilise son complément à un et on le définit comme négatif.

54. if %Nibble7% LSS 0 (

55. set /a "Nibble7=~Nibble7"

56. set "NegNum=true"

57. )

58. rem Divisions successives par 16

59. for /l %%A in (0 1 6) do set /a "Nibble%%A=Nibble7 %% 16", "Nibble7/=16"

60. rem Si le nombre est négatif, la valeur de chaque chiffre ^(Nibble^) est retranché à 15.

61. if /i "%NegNum%"=="true" for /l %%A in (0 1 7) do set /a "Nibble%%A=15 - Nibble%%A"

62. rem Chaque chiffre ayant une valeur supérieure à 9 est transformé en sa représentation hexadécimale.

63. for /l %%A in (0 1 7) do (

64. if !Nibble%%A! EQU 10 set "Nibble%%A=A"

65. if !Nibble%%A! EQU 11 set "Nibble%%A=B"

66. if !Nibble%%A! EQU 12 set "Nibble%%A=C"

67. if !Nibble%%A! EQU 13 set "Nibble%%A=D"

68. if !Nibble%%A! EQU 14 set "Nibble%%A=E"

69. if !Nibble%%A! EQU 15 set "Nibble%%A=F"

70. )

71. if %PrefixEnable% EQU 1 (

72. if %UpperPrefix% EQU 1 (

73. echo 0X%Nibble7%%Nibble6%%Nibble5%%Nibble4%%Nibble3%%Nibble2%%Nibble1%%Nibble0%

74. ) else (

75. echo 0x%Nibble7%%Nibble6%%Nibble5%%Nibble4%%Nibble3%%Nibble2%%Nibble1%%Nibble0%

76. )

77. ) else (

78. echo %Nibble7%%Nibble6%%Nibble5%%Nibble4%%Nibble3%%Nibble2%%Nibble1%%Nibble0%

79. )

80. goto End

83. :Help

84. echo.

85. echo %ScriptName% [/p^|/P] /hex ^<number^>

86. echo %ScriptName% {/?^|-?^|/h^|-h^|/help^|-help}

87. echo.

88. echo     /hex    Définit le ^<number^> qui doit être exprimé en hexadécimal.

89. echo     /p      Définit que le préfixe doit être affiché en minuscules.

90. echo     /P      Définit que le préfixe doit être affiché en majuscules.

91. echo.    /?      Affiche cette aide.

92. echo.

94. :End

95. if not "%~0"==":Help" endlocal

96. exit /b 0

VII - La gestion des flux

Depuis le début de ce document, nous avons utilisé des flux de données sans le savoir. Dans le cas d'un script batch, un flux de données est l'ensemble des données textuelles qu'une commande reçoit pour son exécution et qu'elle produit pendant cette même exécution. Ce flux de données transite via « le canal standard ».

VII-A - Le canal standard

Lors de l'exécution d'une commande, l'interpréteur alloue un espace de mémoire supplémentaire pour cette commande. Cet espace de mémoire est appelé « canal standard », il permet aux commandes exécutées de recevoir des données et de renvoyer des messages d'erreurs ou de réussites. Le canal standard a une entrée (aussi appelée « entrée standard » ou « STDIN »), une sortie (aussi appelée « sortie standard » ou « STDOUT ») et une troisième partie réservée aux messages d'erreurs (appelés « erreurs standards » ou « STDERR »). Le flux de données arrive dans l'entrée standard, il est traité par la commande puis un message de réponse est envoyé dans la sortie standard. Si une erreur survient pendant l'exécution de la commande, le message d'erreur (s'il y en a un) est placé dans l'erreur standard. Par défaut, dès qu'une donnée est placée dans la sortie standard ou dans l'erreur standard, elle est affichée par l'interpréteur. Chaque partie du canal standard est désignée par un numéro qui lui est propre, ce numéro est appelé « handle ». Il permet à l'interpréteur d'identifier la zone de mémoire réservée à chaque partie du canal. Notez qu'un handle désigne un fichier chargé en mémoire, l'interpréteur traite les constituants du canal standard comme des fichiers chargés en mémoire. Dans la suite de ce chapitre, nous utiliserons le terme « tampon » pour désigner un fichier chargé en mémoire afin de ne pas induire le lecteur en erreur. Le schéma ci-dessous représente le déroulement de l'exécution d'une commande.

VII-B - Les opérateurs de redirection

Afin de pouvoir contrôler les « mouvements » d'un flux, l'interpréteur fournit les opérateurs de redirection qui permettent, par exemple, d'envoyer des données dans un fichier ou de lire les données contenues dans un fichier. Le tableau ci-dessous donne une liste des différents opérateurs de redirection, leur syntaxe ainsi qu'une courte description.

Opérateur

Syntaxe

Description

&

Cmd1 & Cmd2

Cmd1 est exécuté puis, quel que soit son résultat, Cmd2 est exécuté.

&&

Cmd1 && Cmd2

Cmd1 est exécuté, puis si et seulement si Cmd1 ne produit pas d'erreur, Cmd2 est exécuté.

|

Cmd1 | Cmd2

Cmd1 est exécuté puis la sortie (STDOUT) de Cmd1 est envoyée dans l'entrée (STDIN) de Cmd2.

||

Cmd1 || Cmd2

Cmd1 est exécuté puis, si et seulement si Cmd1 produit une erreur, Cmd2 est exécuté.

<

Cmd1 < File

Le contenu du fichier (File) est envoyé dans l'entrée (STDIN) de Cmd1.

>

Cmd1 > File

Cmd1 est exécuté, sa sortie (STDOUT) est envoyée dans un fichier(File). L'opération s'effectue en écriture seule, le fichier de destination n'est pas lu et cela a pour effet de le remplacer par un fichier ne contenant que les nouvelles données.

Handle>File

Le contenu du tampon désigné par l'Handle est copié dans le fichier (File) de destination. L'opération s'effectue en écriture seule, le fichier de destination n'est pas lu et cela a pour effet de le remplacer par un fichier ne contenant que les nouvelles données.

>>

Cmd1 >> File

Cmd1 est exécuté, sa sortie (STDOUT) est envoyée dans un fichier (File). L'opération s'effectue en lecture et écriture, les données contenues dans le fichier de destination sont lues puis la sortie de Cmd1 est ajoutée à la fin.

Handle>>File

Le contenu du tampon désigné par l'Handle est copié dans le fichier (File) de destination. L'opération s'effectue en lecture et écriture, les données contenues dans le fichier de destination sont lues puis le contenu du tampon désigné par l'Handle est ajouté à la fin.

<&

<& Handle

Redirige l'entrée standard (STDIN) dans le tampon désigné par l'Handle.

Handle1 <& Handle2

Redirige le flux entrant dans le tampon désigné par l'Handle1 dans le tampon désigné par l'Handle2.

>&

>& Handle

Redirige la sortie standard (STDOUT) dans le tampon désigné par l'Handle.

Handle1>& Handle2

Redirige le flux sortant du tampon désigné par l'Handle1 dans le tampon désigné par l'Handle2.

Tableau 14 : Opérateurs de redirection

VII-C - L'opérateur &

Cet opérateur permet d'exécuter plusieurs commandes, les unes à la suite des autres, sans tenir compte de ce qui s'est passé durant l'exécution de la commande précédente. Ainsi, chaque fois que l'interpréteur rencontre l'opérateur &, il sait qu'une autre commande est à exécuter, on parle d'exécution séquentielle.

Script 71

1. @echo off & cls

2. echo Affichage:

3. echo. & echo. & echo 1 & echo 2

4. echo. &  echo.

5. pause & exit /b 0

VII-D - La sortie standard

La sortie standard (à ne pas confondre avec l'affichage standard que l'on a vu au chapitre I) reçoit les messages d'une commande exécutée dans l'interpréteur. Le meilleur exemple d'utilisation de la sortie standard est la commande echo qui ne fait qu'envoyer les données qui lui sont passées en paramètres dans la sortie standard. Chaque fois qu'une chaîne de caractères est envoyée dans la sortie standard, l'interpréteur l'affiche aussitôt. Il est possible de rediriger la sortie standard vers un fichier ou dans l'entrée d'une autre commande. Si la sortie standard est redirigée, tout ce qui est envoyé dans la sortie standard est ensuite placé dans sa nouvelle destination et rien n'est affiché dans l'interpréteur. La sortie standard peut-être désignée par son handle (1) ou de façon implicite si le caractère > est utilisé dans l'opérateur de redirection. Les redirections peuvent être placées avant ou après la commande, cela ne change rien à leurs significations. Le schéma ci-dessous représente l'exécution d'une commande avec redirection de la sortie standard.

VII-E - Les opérateurs > et >>

Ces opérateurs permettent de rediriger la sortie standard ou le contenu d'un tampon vers un fichier. Seul le mode d'ouverture du fichier de destination diffère entre ces deux opérateurs. Ainsi, avec l'opérateur >, le fichier de destination n'est pas lu, les données sont donc effacées par l'écriture des nouvelles données. Avec l'opérateur >>, le fichier de destination est lu puis les nouvelles données sont ajoutées à la suite de celles déjà contenues dans le fichier de destination. Dans le script 72, la chaîne foo n'est pas dans le fichier Output.txt lorsqu'il est affiché par la commande type Output.txt, car le fichier n'a pas été lu lors de l'ajout de la chaîne bar.

Script 72

1. @echo off

2. echo foo>Output.txt

3. echo bar>Output.txt

4. echo foobar>>Output.txt

5. type Output.txt

6. del /Q Output.txt

7. pause

8. exit /b 0

VII-F - L'erreur standard

L'erreur standard reçoit les messages d'erreurs d'une commande, ces informations sont des chaînes de caractères exposant, en langage humain, où se trouve l'erreur. Par défaut, l'interpréteur affiche les données contenues dans l'erreur standard à moins que le flux ne soit redirigé vers un fichier. L'handle de l'erreur standard est le 2 et elle n'est jamais utilisée de façon implicite, elle doit donc toujours être spécifiée dans la redirection. Il faut aussi noter que certaines commandes permutent les handles 1 et 2, cela est dû à de mauvaises pratiques de programmation.

Script 73

1. @echo off

2. rem La commande « set /a "Err=09" » génère toujours une erreur

3. 2>Log.txt set /a "Err=09"

4. echo La commande a été exécutée.

5. echo.

6. type Log.txt

7. del /Q Log.txt

8. echo.

9. echo.

10. rem Dans le cas de la commande « change port /? » , le message est envoyé dans l'erreur standard alors qu'il n'y a pas d'erreur.

11. change port /? 2>>HelpChangePort.txt

12. echo Syntaxe de change port:

13. echo.

14. type HelpChangePort.txt

15. del /Q HelpChangePort.txt

16. echo.

17. echo.

18. pause

19. exit

VII-G - L'entrée standard

L'entrée standard est tout ce que la commande reçoit comme données, elle comprend la saisie au clavier de l'utilisateur (s'il y en a une) ainsi que le ou les fichiers qui seront envoyés par ce biais. L'handle de l'entrée standard est 0, mais elle peut aussi être utilisée de manière implicite si le caractère < est utilisé dans l'opérateur de redirection. Ci-dessous le schéma de l'exécution d'une commande avec redirection vers l'entrée standard.

Exemple avec le script 74 :

Script 74

1. @echo off

2. 1>>test.txt echo foo

3. 1>>test.txt echo bar

4. 1>>test.txt echo foobar

5. <test.txt more

6. del /q test.txt

7. pause

8. exit /b 0

L'entrée standard ne peut être redirigée directement vers un fichier sur le disque dur, les données doivent d'abord être redirigées vers un tampon avant de pouvoir être redirigées vers un fichier, c'est dû au fait que cela n'a théoriquement pas de sens. Exemple avec le script 75 :

Script 75

1. @echo off

2. cls

3. rem Création d'un fichier texte

4. 1>test.txt echo     Ligne 1

5. 1>>test.txt echo     Ligne 2

6. rem Utilisation explicite de l'entrée standard

7. echo Redirection 1:

8. 0>>LogInput.txt type test.txt

9. echo Output 1:

10. type LogInput.txt

11. del /Q LogInput.txt

12. echo.

13. rem L'handle 3 désigne un tampon défini par l'utilisateur.

14. echo Redirection 2:

15. 0<&3>>LogInput.txt type test.txt

16. echo Output 2:

17. type LogInput.txt

18. del /Q LogInput.txt

19. echo.

20. rem Utilisation implicite de l'entrée standard.

21. echo Redirection 3:

22. <&3>logInput.txt type test.txt

23. echo Output 3:

24. type LogInput.txt

25. del /Q LogInput.txt

26. echo.

27. del /Q test.txt

28. pause

29. exit /b 0

Comme vous pouvez le voir dans l'affichage du script 75, lorsqu'on redirige l'entrée standard directement depuis l'handle 0 vers le fichier de sortie, rien ne se produit ; en effet, il n'est pas possible de rediriger l'entrée standard directement, il faut rediriger son contenu dans un autre tampon pour pouvoir le rediriger vers un fichier. Dans le deuxième et le troisième cas de notre exemple, l'entrée standard est redirigée dans le tampon désigné par l'handle 3 avant d'être redirigée vers le fichier, la commande type test.txt ne sert ici qu'à charger le fichier dans l'entrée standard.

VII-H - Le pseudo-périphérique NUL

Un pseudo-périphérique dénommé NUL est également disponible (si on le compare avec les systèmes de type Unix, ce serait le périphérique NUL, accessible via le point de montage /dev/nul). Il renvoie les données nulle part , celles-ci seront simplement supprimées du tampon source et seront donc définitivement perdues. Ce pseudo-périphérique trouve son utilité dans un certain nombre de cas comme lorsqu'on ne veut pas que la sortie d'une commande apparaisse dans l'affichage de l'interpréteur. Ce pseudo-périphérique n'a pas d'handle, mais un pseudo-fichier de périphérique afin d'être traité par l'interpréteur comme étant un fichier. Il n'a pas d'utilisation implicite et il ne devrait pas non plus être utilisé en entrée puisqu'il ne contient aucune donnée. Il peut donc être utilisé en sortie de tous les autres tampons. Il est utilisable via le pseudo-fichier nul, comme dans le script 76.

Script 76

1. @echo off

2. echo test 1>nul

3. set /a "Var=09" 2>nul

4. set /p "Var2=Entrez une chaîne:    " 0>nul

5. echo.

6. echo %Var2%

7. pause

8. exit

Dans le script 76, la commande set /p "Var2=Entrez une chaîne: " 0>nul se termine avant que l'utilisateur ne puisse entrer quoi que se soit, cela est dû à la redirection de l'entrée standard vers le pseudo-périphérique NUL. Ainsi lorsque Var2 est expansée, celle-ci est vide ; ce qui provoque l'affichage de la ligne Commande ECHO désactivée. Si l'on retire la commande echo. du script 76, on obtient l'affichage suivant :

La chaîne d'invite a bien été envoyée dans la sortie standard, mais comme set /p attend des données en provenance de l'entrée standard et que celle-ci est renvoyée vers le pseudo-périphérique NUL, le traitement de la commande est perdu et le retour à la ligne ne se fait jamais. Cela s'avère fort pratique lorsqu'il est nécessaire d'ajouter du texte dans un fichier sans retour à la ligne.

Le pseudo-fichier NUL est réservé par le système, il n'est donc pas possible de créer un fichier portant ce nom (même avec une extension de fichier).

VII-I - L'opérateur |

Cet opérateur, appelé « Pipe », permet de rediriger la sortie d'une commande dans l'entrée de la commande suivante, on parle de « pipe-line » de commande. Cependant, toutes les commandes n'ont pas la capacité de traiter les données contenues dans l'entrée standard ; les commandes qui peuvent faire cela sont appelées des filtres.

VII-I-1 - Les filtres

Il existe plusieurs commandes qui agissent comme des filtres, c'est-à-dire qu'elles ont la capacité de récupérer le contenu de l'entrée standard, d'exécuter leurs traitements en fonction du contenu de l'entrée standard puis d'envoyer leurs messages de résultat dans la sortie standard afin d'être récupérés par un des différents moyens existants tels que les boucles for ou les redirections. Voici une liste non exhaustive des commandes pouvant être utilisées comme filtres.

Commandes

Descriptions

find

Effectue une recherche de sous-chaînes dans les chaînes de l'entrée standard (chaque chaîne de l'entrée standard est séparée par un <CRLF>) puis renvoie les résultats dans la sortie standard.

sort

Effectue un tri par ordre alphabétique dans les chaînes de l'entrée standard (chaque chaîne de l'entrée standard est séparée par un <CRLF>) puis renvoie les résultats dans la sortie standard.

more

Récupère l'entrée standard puis envoie les données par paquets de tailles équivalentes à la taille de la fenêtre de l'interpréteur dans la sortie standard pour qu'ils soient affichés.

findstr

Effectue une recherche de sous-chaînes dans les chaînes de l'entrée standard (chaque chaîne de l'entrée standard est séparée par un <CRLF>) puis renvoie les résultats dans la sortie standard.

Tableau 15 : Les filtres

N.B. : <CRLF> est un retour à la ligne, formaté Windows, d'une taille de deux octets.

Exemple avec le script 77.

Script 77

1. @echo off

2. (echo foo & echo bar & echo foobar & echo test & echo retest & echo yes & echo no)>>test.txt

3. echo Affichage de sortie du filtre Sort :

4. type test.txt|sort

5. echo.

6. echo Affichage de sortie du filtre Find :

7. type test.txt|find "test"

8. echo.

9. echo Affichage de sortie du filtre Findstr :

10. type test.txt|findstr test

11. echo.

12. pause

13. mode con cols=80 lines=5

14. type test.txt|more

15. echo.

16. del /Q test.txt

17. pause

18. exit /b 0

VII-I-2 - Le filtre find

Find est une commande simple de recherche de chaînes, mais elle n'en est pas moins utile pour une recherche rapide et peu complexe de chaînes. Elle prend plusieurs paramètres tels que /i qui rend la recherche insensible à la casse, /n qui affiche les numéros des lignes trouvées, /c qui n'affiche que le nombre de lignes trouvées et /v qui ne recherche que les lignes ne contenant pas la chaîne.

Dans le script 78, le pipe doit être échappé afin de ne pas provoquer d'erreur. En effet, la transition de la commande du contexte du script vers celui de l'ensemble se fait sous forme de chaîne de caractères, il faut donc traiter la commande comme telle.

Script 78

1. @echo off

2. (echo Contenu de la ligne 1 & echo Contenu de la ligne 2) 1>>text.txt

3. for /f "usebackq delims=" %%A in (`type text.txt^|find /n /v ""`) do (

4. for /f "tokens=1-2 delims=[]" %%a in ("%%A") do echo Ligne %%a: %%b

5. )

6. del /Q text.txt

7. pause

8. exit /b 0

VII-I-3 - Le filtre sort

La commande sort effectue un tri par ordre alphabétique entre les chaînes qui lui sont passées en paramètres, ce qui permet de formater la sortie un peu comme un dictionnaire. Elle prend plusieurs paramètres tels que /r qui inverse l'ordre de tri, /o qui définit un fichier de sortie pour le tri, /t qui définit un répertoire temporaire pour l'exécution du tri, /l qui définit les paramètres régionaux pour l'exécution du tri ou encore /+n qui indique à partir de quel caractère commence le tri (ou n est l'index du premier caractère en partant de 1).

Script 79

1. @echo off

2. 1>text.txt  echo zxy0

3. 1>>text.txt echo abc9

4. 1>>text.txt echo 9abc

5. 1>>text.txt echo 0zxy

6. echo Trie 1:

7. for /f "delims=" %%a in ('type text.txt^|sort /r') do (

8. echo %%a

9. )

10. echo. & echo Trie 2:

11. for /f "delims=" %%a in ('type text.txt^|sort /+2') do (

12. echo %%a

13. )

14. del /Q text.txt

15. echo.

16. pause

17. exit /b 0

VII-I-4 - Le filtre more

La commande more s'utilise généralement pour formater un affichage, mais elle peut aussi être utilisée dans le but de formater un lot de données dont le traitement nécessite une taille particulière ou de remplacer les tabulations par des espaces.

Script 80

1. @echo off

2. 1>test.txt echo Tab1:    "

3. 1>>test.txt echo Tab2:        "

4. 1>>test.txt echo Tab3:            "

5. echo le texte:

6. type test.txt

7. echo.

8. echo la sortie:

9. for /f "delims=" %%A in ('type test.txt^|more /T1') do (

10. echo %%A

11. )

12. del /Q test.txt

13. pause

14. exit /b 0

VII-I-5 - Le filtre findstr

La commande findstr permet des recherches de chaînes, mais à la différence de find, elle supporte des regexs. Même si celles-ci sont plutôt pauvres aux vues de celles dont disposent d'autres langages tels que Perl ou des regexs POSIX, elles n'en sont pas moins utiles pour traiter des chaînes. Voici une liste des motifs qu'elles supportent :

Motif

Description

.

Caractère joker, correspond à une occurrence de tout caractère quel qu'il soit.

*

Répétition, zéro occurrence ou plus du caractère ou de la classe le précédent.

^

Motif de début de ligne.

$

Motif de fin de ligne.

\x

Utilisation littérale du métacaractère x.

\<xyz

Motif de début de mot.

xyz\>

Motif de fin de mot.

[classe]

Classe de caractères, correspond à tout caractère compris dans la classe.

[^classe]

Classe inverse, correspond à tout caractère non compris dans la classe.

[x-y]

Limites, correspond à tout caractère dans les limites spécifiées.

Tableau 16 : Motifs des regexs de findstr

Vous aurez sûrement remarqué que certains de ces caractères sont significatifs pour l'interpréteur. En effet, les caractères ^<> doivent être échappés pour pouvoir être utilisés dans une regex de findstr, que ça soit dans un script ou dans l'interpréteur. Ainsi pour trouver la chaîne <?xml version="1.1" encoding="UTF-8" ?>, il est nécessaire d'utiliser la regex suivante :

^^^<?xml version="1\.[0-1]*" encoding="[0-9a-z\-]*" ?^>$

Il faut distinguer deux types d'échappement dans la regex précédente : les échappements inhérents à la ligne de commande et les échappements propres à la regex. Ainsi, les échappements de la ligne de commande sont ceux exposés au chapitre I, soit <>|^&% ; et les échappements de la regex sont tous les métacaractères d'une regex, soit .*$\[]-. Ces caractères propres à la regex doivent être échappés avec le caractère \. Dans le cas des motifs de début et de fin de mot (<>), ces caractères doivent être échappés avec le caractère \ pour signifier à la regex qu'ils sont des métacaractères de la regex et non pas des caractères de la chaîne.

Script 81

1. @echo off

2. cls

4. (

5. echo foo

6. echo fao

7. echo fa

8. echo bar0

9. echo bar1

10. echo bar2

11. echo bar3

12. echo barfoo

13. echo ^<?xml version="1.1" encoding="UTF-8" ?^>

14. echo ^^*

15. echo [MySection]

16. echo C:\Windows\System32\cmd.exe

17. )>>txt

19. echo Contenu du fichier :

20. type txt

21. echo.

22. echo.

24. echo Début de chaîne(^^bar):

25. type txt|findstr ^^bar

26. echo.

28. echo Fin de chaîne(1$):

29. type txt|findstr 1$

30. echo.

32. echo Début de mot(\^<fa):

33. type txt|findstr \^<fa

34. echo.

36. echo Fin de mot(2\^>):

37. type txt|findstr 2\^>

38. echo.

40. echo Caractère unique(\^<f.\^>):

41. type txt|findstr \^<f.\^>

42. echo.

44. echo Zéro ou plus doccurrences du caractère o(\^<fo*\^>):

45. type txt|findstr \^<fo*\^>

46. echo.

48. echo Dans une classe de caractères(\^<f[ao]o\^>):

49. type txt|findstr \^<f[ao]o\^>

50. echo.

52. echo Zéro ou plus d'occurrences de la classe de caractères[ao](\^<f[ao]*\^>):

53. type txt|findstr \^<f[ao]*\^>

54. echo.

56. echo Intervalle de caractères dans une classe(\^<bar[0-9]\^>):

57. type txt|findstr \^<bar[0-9]\^>

58. echo.

60. echo Hors de la classe de caractères(\^<bar[^^12]\^>):

61. type txt|findstr \^<bar[^^12]\^>

62. echo.

64. echo Hors d'un intervalle de caractères dans une classe(\^<bar[^^1-9]\^>):

65. type txt|findstr \^<bar[^^1-9]\^>

66. echo.

68. echo Zéro ou plus d'occurrences hors d'un intervalle de caractères dans une classe(\^<bar[^^0-9]*\^>):

69. type txt|findstr \^<bar[^^0-9]*\^>

70. echo.

72. echo Déclaration XML ("^^^<?xml version="1\.[0-1]*" encoding="[0-9a-z\-]*" ?^>$"):

73. type txt|findstr "^^^<?xml version="1\.[0-1]*" encoding="[0-9a-z\-]*" ?^>$"

74. echo.

76. echo Métacaractère(\^<[\^^\*]*\^>):

77. type txt|findstr \^<[\^^\*]*\^>

78. echo.

80. echo Section *.ini(\^<\[[a-z]*\]\^>):

81. type txt|findstr \^<\[[a-z0-9]*\]\^>

82. echo.

84. echo Chemin d'accès(\^<[a-z:]*\\[a-z]*\\System32\\[a-z]*\.[a-z]*\^>):

85. type txt|findstr /i \^<[a-z:]*\\[a-z]*\\System32\\[a-z]*\.[a-z]*\^>

86. echo.

88. del /Q txt

89. pause

90. exit /b 0

VII-J - L'opérateur <

Cet opérateur place le contenu d'un fichier dans l'entrée standard pour l'exécution d'une commande. Cette dernière doit être un filtre pour pouvoir traiter les données contenues dans l'entrée standard.

Script 82

1. @echo off

2. 1>text.txt  echo foo

3. 1>>text.txt echo bar

4. 1>>text.txt echo foobar

5. echo Sort:

6. echo.

7. sort <text.txt

8. echo.

9. echo.

10. echo Findstr:

11. echo.

12. <text.txt findstr /i \^<foo

13. echo.

14. echo.

15. del /Q text.txt

16. pause

17. exit /b 0

VII-K - Le point sur « set /p » et « type »

set /p et type ne sont pas des filtres, même s'ils utilisent l'entrée standard, ils ne récupèrent pas son contenu. set /p utilise la sortie standard avant d'attendre des données en provenance de l'entrée standard ; cela a pour effet de supprimer toutes les données qui s'y trouvent. Il est donc inutile de tenter de se servir de set /p ainsi, car cela ne fonctionnera jamais. En ce qui concerne type, le contenu du fichier à afficher est placé dans l'entrée standard puis renvoyé dans la sortie standard, toutes les données présentes au moment du chargement du fichier dans l'entrée standard seront écrasées. Il n'est donc pas plus possible de tenter ce genre de chose avec type.

VII-L - Les opérateurs && et ||

Ces opérateurs permettent de gérer les erreurs dans un script. L'opérateur && permet d'exécuter une commande seulement si la commande précédente n'a pas provoqué d'erreur ; ainsi la seconde commande ne s'exécute qu'en cas de réussite de la première. Exemple avec le script 83 :

Script 83

1. @echo off

2. 2>nul set "var=foo" && 2>nul set "var2=bar"

3. set var

4. echo.

5. 2>nul set /a "nb=09" && 2>nul set /a "nb2=07"

6. set nb

7. echo.

8. pause

9. exit /b

L'opérateur || n'exécute la commande qui le suit seulement si la commande qui le précède provoque une erreur, ainsi la seconde commande ne s'exécute qu'en cas d'erreur de la première ; exemple avec le script 84.

Script 84

1. @echo off

2. 2>nul set "var=foo" || echo Erreur de la commande : set "var=foo"

3. echo.

4. 2>nul set /a "nb=09" || echo Erreur de la commande : set /a "nb=09"

5. echo.

6. pause

7. exit /b

VII-L-1 - Tester les deux états d'erreurs

Il peut être nécessaire de tester si une commande s'est exécutée avec ou sans erreurs et effectuer des actions différentes dans chacun des deux cas. La combinaison des opérateurs && et || doit se faire selon un ordre précis afin de ne pas générer d'erreurs. En utilisant le test de réussite (&&) avant le test d'erreur (||), le test de réussite n'est valide que lorsqu'il n'y a aucune erreur dans la commande et le test d'erreur n'est valide que lorsqu'il y a une erreur au test de réussite . Exemple avec le script 85 :

Script 85

1. @echo off &cls

2. echo Etat     Méthodes     Commandes

3. echo ====================================

5. rem Méthode 1

6. (

7. 2>nul set /a "var=09"

8. ) || (

9. echo [Erreur] 1           set /a "var=09"

10. ) && (

11. echo [Ok]     1           set /a "var=09"

12. )

13. (

14. set /a "var=07"

15. ) || (

16. echo [Erreur] 1           set /a "var=07"

17. ) && (

18. echo [Ok]     1           set /a "var=07"

19. )

20. echo ====================================

22. rem Méthode 2

23. (

24. 2>nul set /a "var=09"

25. ) && (

26. echo [Ok]     2           set /a "var=09"

27. ) || (

28. echo [Erreur] 2           set /a "var=09"

29. )

30. (

31. set /a "var=07"

32. ) && (

33. echo [Ok]     2           set /a "var=07"

34. ) || (

35. echo [Erreur] 2           set /a "var=07"

36. )

38. echo ====================================

39. echo.

40. pause

41. exit /b 0

VII-M - Les opérateurs <& et >&

Ces opérateurs permettent de rediriger le flux d'un tampon à l'autre. L'opérateur <& redirige l'entrée standard ou le flux entrant dans un tampon vers un autre ; l'opérateur >& redirige la sortie standard ou le flux sortant d'un tampon vers un autre. Ces opérateurs ne peuvent être utilisés qu'avec des handles, toutes les redirections entre des tampons doivent se faire avec ces opérateurs.

Script 86

1. @echo off

2. cls

3. 3>>errlog.txt set /a "var1=09" 2>&3

4. 3>>errlog.txt echo foo 1>&3

5. 3>>errlog.txt echo bar 1<&3

6. 3>>errlog.txt echo foobar >&3

7. echo Fichier errlog.txt:

8. echo.

9. type errlog.txt

10. echo.

11. del /Q errlog.txt

12. pause

13. exit /b 0

VII-N - Les handles pour tampon utilisateur

D'autres handles sont disponibles, ce sont les handles pour tampons utilisateurs. Ils sont utilisés afin de rediriger l'entrée standard et/ou de disposer d'un ou plusieurs tampons supplémentaires lors d'une exécution. Les handles pour tampons utilisateurs sont les handles 3 à 9 et l'utilisation qui est faite de ces derniers est définie par l'utilisateur. De même que pour les tampons du canal standard, si les données qu'ils contiennent ne sont pas redirigées vers un fichier, celles-ci seront affichées dans la fenêtre de l'interpréteur.

Un tampon n'est accessible que durant l'exécution qui l'a chargé en mémoire ; ainsi lorsqu'il est utilisé dans une ligne de commande, ce tampon n'existe que durant l'exécution de cette ligne de commande. Pour pouvoir utiliser l'handle d'un tampon utilisateur pendant l'exécution de plusieurs commandes, il faut que l'handle désigne toujours le même tampon;  de plus, les handles sont assignés à des fichiers chargés en mémoire, il faut donc aussi un fichier à charger en mémoire pour qu'il puisse servir de tampon.

Les scripts batch ne gère nativement que les chaînes de caractères, le fichier qui servira de tampon doit donc être un fichier texte, il est créé au moment de la première utilisation et doit être supprimé manuellement, car une fois créé, il est enregistré sur le disque dur.

Dans un script batch, un bloc de commandes se symbolise avec des parenthèses () comme dans les conditions if où les commandes entre parenthèses ne sont exécuté que si la condition se vérifie ; ou encore dans les boucles for où le bloc de commandes entre parenthèses s'exécute à chaque itération de la boucle. Chaque fois que l'interpréteur rencontre une parenthèse ouvrante, il considère toutes les lignes de commandes jusqu'à la parenthèse fermante équivalente comme faisant partie d'un seul et même bloc indissociable. Les redirections agissent aussi sur ces blocs, il est ainsi possible de rediriger la sortie standard de chaque commande du bloc en plaçant une redirection avant ou après le bloc ; la redirection agira sur la totalité du bloc.

De la même manière, la redirection d'un tampon utilisateur peut agir sur un bloc de commandes ; le fait de rediriger un tampon utilisateur pour un bloc de commandes permet de lier le fichier texte et l'handle pour tout le bloc de commandes. Chaque fois qu'il sera fait référence à l'handle dans le bloc de commandes, l'handle désignera toujours le même fichier texte. Le fichier sera alors accessible via l'handle et via son nom. Cependant, durant l'exécution du bloc, le fichier ne peut ni être vidé ni être supprimé. Ainsi pour créer un bloc dans lequel l'handle 3 désigne le fichier buffer3, il faudrait utiliser la syntaxe suivante :

Syntaxe de la redirection de bloc

3>>buffer3 (
:: Commande du bloc
)

Dans le script 87, des chaînes de caractères sont redirigées vers le tampon désigné par l'handle 3, le tampon est ensuite parcouru via le nom du fichier chargé dans le tampon désigné par l'handle 3. La commande 0>nul >&3 set /p "=xyz" sert à ajouter la chaîne xyz au tampon désigné par l'handle 3 sans retour à la ligne.

Script 87

1. @echo off

2. cls

3. 3>>buffer3 (

4. 1<&3 echo Foo

5. 1>&3 echo Bar

6. 0>nul >&3 set /p "=Foo"

7. >&3 echo Bar

8. for /f "delims=" %%a in ('type buffer3') do (

9. 0>nul >&3 set /p "=%%a "

10. )

11. >&3 echo.

12. )

13. type buffer3

14. del /Q buffer3

15. pause

16. exit /b 0

De la même façon, il est possible d'utiliser les redirections avec les blocs des conditions et des boucles ; exemple avec le script 88.

Script 88

1. @echo off

2. 1>text.txt  echo foo

3. 1>>text.txt echo bar

4. 1>>text.txt echo foobar

5. if exist text.txt 9>>buffer9 (

6. >&9 echo Le fichier text.txt a été trouvé.

7. )

8. echo Contenu de buffer9: & type buffer9 & del /Q buffer9 & echo.

9. for /f "delims=" %%a in ('type text.txt') do (((

10. echo %%a | findstr \^<foo 1>&3

11. echo %%a | findstr \^<bar 1>&4

12. echo %%a | findstr bar\^> 1>&5

13. ) 5>>buffer5

14. ) 4>>buffer4

15. ) 3>>buffer3

16. echo Contenu de buffer3: & type buffer3 & del /Q buffer3 & echo.

17. echo Contenu de buffer4: & type buffer4 & del /Q buffer4 & echo.

18. echo Contenu de buffer5: & type buffer5 & del /Q buffer5 & echo.

19. del /Q text.txt

20. pause

21. exit

VII-O - Les pseudo-fichiers de périphérique

Les pseudo-fichiers de périphériques sont des alias de « nom de fichier » qui permettent d'accéder au contenu d'un tampon de périphérique de la même manière qu'un fichier texte. Plusieurs périphériques sont disponibles depuis l'interpréteur, en voici la liste :

- le canal standard ;

- les ports séries ;

- les ports parallèles ;

- l'imprimante par défaut (seulement pour les systèmes Vista et inférieurs, et certains Windows 7) ;

- le périphérique auxiliaire.

VII-O-1 - Le pseudo-fichier CON

Pour rappel le canal standard est le tampon utilisé par les commandes durant leurs exécutions, il est traité comme étant un périphérique du fait que l'entrée standard est liée par défaut au clavier et la sortie standard est liée par défaut à l'écran (la fenêtre de l'interpréteur). Le pseudo-fichier du canal standard est CON[:], il a assez peu d'applications concrètes, mais en voici une qui permet de saisir un texte sur plusieurs lignes à la différence de la commande set /p qui ne permet les saisies que sur une seule ligne.

Script 89

1. @echo off

2. for /f "delims=" %%a in ('type CON') do (

3. echo %%a

4. )

5. pause

6. exit

N.B. Pour quitter la saisie et continuer l'exécution du script, il faut presser les touches Ctrl+Z et appuyer sur entrée. Voir l'annexe A.2 pour plus de détails.

Le nom de fichier CON est réservé par le système, il n'est donc pas possible de créer un fichier qui porte ce nom. Les différentes constituantes du canal standard sont accessibles via le pseudo-fichier CON, ainsi l'entrée standard est accessible via CON[:]IN$, la sortie standard via CON[:]OUT$ et l'erreur standard via CON[:]ERR$.

VII-O-2 - Les pseudo-fichiers COMx

Les ports séries utilisent les pseudo-fichiers COMx, où x est le numéro de port cible. Ce numéro peut aller de 1 à 256 même si un PC ne peut avoir 256 ports (ces numéros de ports sont attribuables à des ports mappés sur un réseau). L'utilisation des ports est plutôt simple, car il suffit de les lire ou les écrire via les commandes echo (pour l'écriture) et type ou more (pour la lecture). Il faut prendre en compte qu'un port série doit être configuré avec les mêmes paramètres que le port distant auquel il est connecté, cela peut être fait via la commande mode COMx ; l'affichage des paramètres se fait avec mode COMx /status. La configuration d'un port série ne seras pas abordée dans ce document du fait de la quantité d'éléments à prendre en compte. Vous pouvez effectuer des tests d'écritures/lectures sur un port série en utilisant une « loopback » entre deux ports séries d'un même ordinateur à l'aide d'un câble croisé 3, 5 ou 7 fils, il suffit alors de relier les deux ports séries avec le câble croisé. Les pseudo-fichiers COM1 à COM9 sont réservés par le système et les autres le seront si un port distant est mappé dessus. De même que pour le canal standard, les entrées et les sorties sont accessibles via COMx:IN$ et COMx:OUT$.

Écriture sur un port série, où <message> est la chaîne a envoyer par le port série.

echo <message> >COMx

Lecture depuis un port série.

type COMx

more<COMx

VII-O-3 - Les pseudo-fichiers LTPx

Les ports parallèles utilisent les pseudo-fichiers LTPx, où x est le numéro de port cible. Ce numéro peut aller de 1 à 256. Il existe un mappage local entre port COM et LTP via la commande mode LTPx[:]=COMy[:]. L'écriture et la lecture s'utilisent de la même façon que pour les ports séries, via les commandes echo et type ou more. Les pseudo-fichiers LTP1 à LTP9 sont réservés par le système et les autres le seront si un port distant est mappé dessus. De même que pour le canal standard, les entrées et les sorties sont accessibles via LTPx:IN$ et LTPx:OUT$.

VII-O-4 - Le pseudo-fichier PRN

L'imprimante par défaut utilise le pseudo-fichier PRN et à la différence des autres pseudo-fichiers de périphérique, il n'est pas possible de lire les données qu'il contient. Ainsi seule l'écriture est possible sur ce pseudo-fichier de périphérique, cela s'effectue à l'aide d'une redirection de la sortie standard. Il faut noter que sous Windows 7 (Ultimate et Entreprise seulement) et Windows Serveur 2008 R2, ce pseudo-fichier de périphérique n'est plus fonctionnel. Le pseudo-fichier PRN est réservé par le système.

VII-O-5 - Le pseudo-fichier AUX

Le périphérique auxiliaire est mappé sur le port COM1 pour des questions de compatibilité ascendante avec les anciens systèmes de la gamme Microsoft. Le mappage de ce port peut être modifié via la commande change port, voir l'aide en ligne de commande pour plus d'informations. Le pseudo-fichier AUX est réservé par le système. Si le pseudo-fichier AUX est mappé sur un autre pseudo-fichier qui possède plusieurs constituants (IN$, OUT$ ou ERR$), alors ces dernières sont accessibles via AUX:IN$, AUX:OUT$ et éventuellement AUX:ERR$.

VIII - Les fichiers de réponse

Un script a besoin de données pour s'exécuter, il arrive parfois que la quantité de données nécessaires soit très importante. Plutôt que d'utiliser une commande à rallonge, il est possible de placer ces données dans un fichier texte puis de les lire avec une boucle for. Cependant un fichier texte classique n'est guère adapté à ce type d'opération. La solution la plus pratique est l'utilisation de fichiers *.ini, ces derniers possèdent une syntaxe bien définie qui permet une lecture intuitive et donc facilite une éventuelle modification. Les fichiers *.ini qui doivent êtres utilisés par un script batch doivent être encodés avec le Code Page correspondant à la langue d'installation du système afin de permettre leur utilisation par un script.

VIII-A - La syntaxe des fichiers « *.ini »

Un fichier *.ini se compose de sections qui distinguent les différentes parties du fichier, chaque section relate les différentes entrées à utiliser pour une opération donnée. Les sections utilisent la syntaxe suivante, où <nom_de_la_section> est le nom que vous donnerez à votre section :

[<nom_de_la_section>]

Des commentaires peuvent être ajoutés afin de donner des informations facilitant la lecture du fichier. Pour insérer un commentaire, il faut utiliser le caractère ; en début de ligne. Exemple avec le fichier test.ini :

test.ini

1. ; un en-tête de fichier sous forme de commentaire,

2. ; celui-ci donne un court descriptif des entrées

3. ; présentes dans le fichier ainsi que la fonction

4. ; qu'il a et à quel script il est rattaché

5. ; (éventuellement son auteur et les droits d'auteur).

7. [Section_One]

8. ; un commentaire qui décrit la section [Section_One]

11. [Section_Two]

12. ; un commentaire qui décrit la section [Section_Two]

Les entrées d'une section peuvent utiliser deux syntaxes, mais on utilise qu'un des deux types de syntaxes par section. Chacune de ces deux syntaxes est utilisée à des fins différentes en fonction des besoins.

La syntaxe des entrées « nommées » est utilisée lorsqu'une entrée est unique dans la section. Cependant, même si une entrée nommée ne peut être utilisée qu'une fois par section, elle est souvent utilisée dans chaque section du fichier. La syntaxe des entrées nommées est la suivante, où <Identifiant> est l'identifiant de l'entrée et <Value> est sa valeur :

<Identifiant>=<Value>

Les entrées « non nommées » quant à elles sont utilisées pour les opérations répétitives qui nécessitent plusieurs paramètres. Par exemple la copie de fichiers qui nécessite au moins un chemin d'accès source et un chemin d'accès de destination. Les entrées non nommées sont écrites avec les paramètres les uns à la suite des autres séparés par des virgules.

<Param1>,<Param2>,...,<ParamN>

VIII-B - Lecture d'un fichier « *.ini »

En général, on utilise un seul et même script par système pour lire les fichiers *.ini. Celui-ci est ensuite appelé par les scripts ayant besoin de lire un fichier *.ini. Un tel script pourrait ressembler au script 90.

Script 90

1. @echo off

2. setlocal

3. setlocal enabledelayedexpansion

5. set "ScriptName=%~n0"

6. set "IniFile="

7. set "IniSection="

8. set "IniEntrie="

9. set "CasseSensitive="

10. set "CurrentSection="

12. :ParseArgs

13. if "%~1"=="" goto Init

14. if /i "%~1"=="/?" goto Help

15. if /i "%~1"=="-?" goto Help

16. if /i "%~1"=="/h" goto Help

17. if /i "%~1"=="-h" goto Help

18. if /i "%~1"=="/help" goto Help

19. if /i "%~1"=="-help" goto Help

20. if /i "%~1"=="/f" (

21. set "IniFile=%~2"

22. shift /1

23. shift /1

24. goto ParseArgs

25. )

26. if /i "%~1"=="/s" (

27. set "IniSection=%~2"

28. shift /1

29. shift /1

30. goto ParseArgs

31. )

32. if /i "%~1"=="/e" (

33. set "IniEntrie=%~2"

34. shift /1

35. shift /1

36. goto ParseArgs

37. )

38. if /i "%~1"=="/i" (

39. set "CasseSensitive=/i"

40. shift /1

41. goto ParseArgs

42. )

43. :BadSyntax

44. for /f "delims=" %%a in ('net helpmsg 87') do echo [ %~1 ] %%a

45. if not "%~0"==":BadSyntax" endlocal

46. exit /b 87

48. :Init

49. if not defined IniFile (

50. call :UnknowError "/f"

51. exit /b 87

52. )

53. if not exist "%IniFile%" (

54. call :UnknowError "%IniFile%"

55. exit /b 87

56. )

57. if defined IniSection goto Exec

58. :UnknowError

59. if "%~1"=="" (

60. call :BadSyntax "/s"

61. ) else (

62. call :BadSyntax "%~1"

63. )

64. echo.

65. call :Help

66. endlocal

67. exit /b 87

69. :Exec

70. for /f "usebackq eol=; delims=" %%a in (`type "%IniFile%"`) do (

71. set "FixLine=%%~a"

72. if "!FixLine:~0,1!"=="[" (

73. for /f "tokens=1 delims=[]" %%b in ("%%a") do (

74. set "CurrentSection=%%~b"

75. )

76. ) else (

77. if %CasseSensitive% "!CurrentSection!"=="%IniSection%" (

78. if defined IniEntrie (

79. for /f "tokens=1 delims==" %%b in ("%%a") do (

80. if %CasseSensitive% "%%~b"=="%IniEntrie%" echo %%a

81. )

82. ) else (

83. echo %%a

84. )

85. )

86. )

87. )

88. goto End

90. :Help

91. echo.

92. echo %ScriptName% /f ^<Ini_File_Path^> /s ^<Ini_Section^> [/e ^<Ini_Entrie^>] [/i]

93. echo %ScriptName% /?

94. echo.

95. echo     /f  Définit le fichier *.ini à parser.

96. echo     /s  Définit la section à renvoyer.

97. echo     /e  Définit l'entrée à renvoyer.

98. echo     /i  Définit que la recherche est insensible à la casse.

99. echo.    /?  Affiche cette aide.

100. echo.

102. :End

103. if not "%~0"==":Help" endlocal

104. exit /b 0

La lecture d'une section comprenant des entrées nommées peut se faire via un script semblable au script 91. Celui-ci lit le fichier stock.ini dans lequel est consigné l'inventaire d'un bar, ainsi lorsque vous demandez au script 91 une boisson, il vous dit s'il y en a et si c'est votre jour de chance. Remplacez ini_reader.bat par le nom que vous donnerez au script 90.

Script 91

1. @echo off

2. setlocal enabledelayedexpansion

3. set "Choix1=Beer"

4. set "Choix2=Vodka"

5. set /a "Addition=0"

6. set /a "NbConsommation=0"

7. :Serveur

8. set ChoixClient=

9. echo.

10. echo Que voulez-vous boire ?

11. echo.

12. echo     1. Bière

13. echo     2. Vodka

14. echo     3. Rien pour l'instant, j'attends quelqu'un

15. echo     4. L'addition, s'il vous plaît ?

16. echo.

17. choice /c 1234 /n

18. set "ChoixClient=%ErrorLevel%"

19. echo.

20. if %ChoixClient% LSS 3 (

21. for /f "delims=" %%a in ('ini_reader.bat /f stock.ini /s !Choix%ChoixClient%!') do set "%%a"

22. if "!bar!"=="lucky" (

23. echo C'est votre jour de chance, on a pas encore été livré, mais il nous en reste.

24. ) else (

25. echo Pas de chance, on a pas encore été livré.

26. )

27. if "!foo!"=="found" (

28. set /a "Addition+=!foobar!"

29. set /a "NbConsommation+=1"

30. )

31. ) else if %ChoixClient% EQU 4 (

32. goto CaisseEnregistreuse

33. )

34. timeout /t 10 /nobreak

35. cls

36. goto Serveur

37. :CaisseEnregistreuse

38. echo Ça vous fera !Addition! euros.

39. echo.

40. if %NbConsommation% GEQ 3 (

41. echo Vous avez trop bu pour conduire, vous devez me laisser vos clés.

42. echo Écoutez, ne le prenez pas comme ça : c'est la loi sinon je risque un procès.

43. echo Moi je vends de l'alcool, je ne vous oblige pas à le boire...

44. )

45. pause

46. exit /b 0

stock.ini

1. ; état des stocks du BatchBar

2. ; foo contient l'état du stock

3. ; bar définit si c'est votre jour de chance

4. ; foobar contient le prix de la boisson

6. [Beer]

7. ; état des stocks de bière

8. foo=found

9. bar=lucky

10. foobar=2

12. [Vodka]

13. ; état des stocks de vodka

14. foo=not found

15. bar=unlucky

16. foobar=5

18. ; l'abus d'alcool est dangereux pour la santé

La lecture d'une section comprenant des entrées non nommées peut se faire via un script semblable au script 92. Celui-ci lit le fichier filelist.ini dans lequel sont consignés les fichiers à copier et ceux à supprimer. Remplacez ini_reader.bat par le nom que vous donnerez au script 90.

Script 92

1. @echo off

2. set "ScriptName=%~n0"

3. echo Copie des fichiers ...

4. for /f "delims=" %%a in ('ini_reader.bat /f filelist.ini /s Copy') do (

5. for /f "tokens=1-3 delims=," %%b in ("%%a") do (

6. call :CopyFunc "%%b" "%%c" "%%d"

7. )

8. )

9. pause

10. echo Suppression des fichiers ...

11. for /f "delims=" %%a in ('ini_reader.bat /f filelist.ini /s Delete') do (

12. for /f "tokens=1-2 delims=," %%b in ("%%a") do (

13. call :DeleteFunc "%%b" "%%c"

14. )

15. )

16. pause

17. exit /b 0

18. :CopyFunc

19. echo     %~3

20. copy %1 %2

21. goto :eof

22. :DeleteFunc

23. echo     %~2

24. del /q %1

25. goto :eof

filelist.ini

1. ; liste de fichiers à copier et à supprimer

2. ; la section Copy contient les fichiers à copier

3. ; la section Delete contient les fichiers à supprimer

5. [Copy]

6. ; liste de fichiers à copier sous la forme <source>,<destination>,<print>

7. .\%ScriptName%.bat,.\%ScriptName%_Copie1.bat,Copie 1 du script ...

8. .\%ScriptName%.bat,.\%ScriptName%_Copie2.bat,Copie 2 du script ...

9. .\%ScriptName%.bat,.\%ScriptName%_Copie3.bat,Copie 3 du script ...

11. [Delete]

12. ; liste de fichiers à supprimer sous la forme <file>,<print>

13. .\%ScriptName%_Copie1.bat,Suppression de la copie 1 du script ...

14. .\%ScriptName%_Copie2.bat,Suppression de la copie 2 du script ...

15. .\%ScriptName%_Copie3.bat,Suppression de la copie 3 du script ...

IX - Sécurité et script batch

La sécurité est un aspect important en informatique, les scripts batch n'échappent pas à la règle. En effet, les possibilités offertes par ce langage de script sont vastes, y compris pour quelqu'un de mal intentionné. Nous allons voir les différentes méthodes utilisables par un intrus et les solutions qu'il est possible d'y apporter. Cependant, les solutions exposées dans ce chapitre ne sont que des pistes de réflexion ; chaque problématique ayant des implications différentes, il ne peut y avoir de solution générique.

IX-A - Les variables

Contrairement à d'autres langages, les variables sont accessibles à tout le monde ou presque. Ainsi, n'importe quel programme peut accéder aux variables d'environnement d'un autre programme, du moment que celui-ci s'exécute en mode « utilisateur » et qu'il est lancé par le même utilisateur. Seuls les programmes qui sont exécutés par d'autres utilisateurs échappent à cette règle, à moins que le programme qui scrute les variables d'un autre soit lancé par un administrateur ou par le système (en tant qu'utilisateur). Pour cette partie du chapitre, il va nous falloir le programme ProcessExplorer de SysInternals disponible à cette URL : https://technet.microsoft.com/en-us/sysinternals/bb896653.aspx.

Une fois ProcessExplorer téléchargé, lancez-le en tant que simple utilisateur, il propose un affichage similaire à celui ci-dessous. Les programmes surlignés en rose ont été lancés par le système (en tant qu'utilisateur) et les programmes surlignés en bleu ont été lancés par l'utilisateur courant.

Dans ProcessExplorer, il est possible d'afficher la ligne de commande ayant appelé chaque programme (utile pour le débogage de scripts). Cela se fait via le menu View>Select Columns…, puis dans l'onglet Process Image cochez Command Line puis validez en cliquant sur Ok. Créez le script 93 et lancez-le, mais sans appuyer sur une touche au moment de la commande pause.

Script 93

1. @echo off

2. set "foo=bar"

3. pause

Une fois le script lancé, allez dans ProcessExplorer puis cliquez sur le processus de votre batch (processus cmd.exe avec comme ligne de commande cmd /c "chemin\d'accès\de\votre\script"). Allez dans l'onglet Environment, vous pourrez y voir la variable foo et sa valeur.

De ce fait, les données sensibles ne devraient jamais être placées dans une variable, sans quoi vous risquez de créer une faille. La solution réside en partie dans les paramètres des boucles et les paramètres d'appel de fonction. Par exemple, dans le script 94, la chaîne bar n'est jamais accessible.

Script 94

1. @echo off

2. for %%a in (bar) do (

3. pause

4. echo %%a

5. call :Label %%a

6. )

7. pause

8. exit /b

9. :Label

10. pause

11. echo %1

12. goto :eof

Cependant, l'appel d'une commande externe ou d'un script avec comme paramètre la donnée sensible la rendra accessible à tout autre programme lancé par le même utilisateur via la commande d'appel. La meilleure solution reste la création d'un utilisateur spécifique à chaque opération sensible et d'appeler le script qui exécute l'opération via la commande runas. La donnée sensible pourra alors être placée dans le registre sous une clé du type HKCU\software\<mon_du_script>, dans ce cas la clé doit être créée par l'utilisateur spécifique à la tâche. Il convient également de modifier les autorisations de la clé (ACL), cela peut-être fait dans regedit via un clic-droit sur la clé puis sélectionnez Autorisations…. Il convient d'interdire toutes opérations, via l'option Refuser, à tout autre utilisateur et groupe sauf l'utilisateur spécifique. Les autorisations du registre peuvent également être modifiées via la commande regini, entrez regini /? dans l'interpréteur de commandes pour plus d'informations. Par la suite, le contenu de la clé pourra être récupéré via la commande reg query ; de préférence dans une boucle for. Exemple avec le script 95 : il est nécessaire d'ajouter le fichier ipmi.reg au registre afin que le script 95 fonctionne.

Script 95

1. @echo off

3. for /f "usebackq skip=2 delims=" %%a in (`reg query "HKCU\software\ipmi\key" /v connect`) do (

4. for /f "tokens=3" %%b in ("%%a") do echo %%b

5. )

7. pause

8. exit /b 0

ipmi.reg

1. Windows Registry Editor Version 5.00

3. [HKEY_CURRENT_USER\Software\ipmi]

5. [HKEY_CURRENT_USER\Software\ipmi\key]

6. "connect"="0123456789"

Un script de ce type pourra alors être appelé avec le script suivant (où <specific_user_name> est le nom de l'utilisateur spécifique et <cmd_script> est la commande d'appel du script 95).

Script 96

1. @echo off

2. runas /User:<specific_user_name> "<cmd_script>"

3. pause

4. exit /b 0

Il convient de modifier également les autorisations (ACL) du script qui effectue la tâche sensible afin d'éviter toute réécriture inappropriée du script, cela peut être fait via un clic-droit sur le fichier, sélectionnez Propriétés puis l'onglet Sécurité. Il convient de modifier les autorisations de la même manière que pour la clé registre. Les autorisations d'un fichier peuvent aussi être modifiées via la commande icacls, entrez icacls /? dans l'interpréteur de commandes pour plus d'informations. Une commande cacls existe aussi, mais elle est à proscrire, car considérée comme obsolète, son support n'est pas garanti à l'avenir.

IX-B - L'injection de commandes

Comme tout langage de script, le batch est sensible à « l'injection ». Cette méthode consiste à insérer des commandes lors d'une entrée (saisie utilisateur ou lecture d'un fichier texte). Cette section est donc consacrée à l'application de la célèbre maxime « Never trust in user input ».

La commande set /p n'est pas sensible à l'injection de commandes, cependant la variable définie par la commande set /p peut contenir des redirections qui injecteront la (ou les) commande(s) lors de son expansion. Exemple avec le script 97 :

Script 97

1. @echo off

3. set /p foo=Entrez une chaîne :

4. echo.

5. echo Vous avez entré: %foo%

6. echo.

8. pause

Si l'utilisateur saisit la chaîne bar & echo foobar, on obtient l'affichage suivant dans lequel on peut voir l'exécution de la commande echo foobar.

La solution est simple et repose sur l'échappement, mais dans une forme que nous n'avons pas encore vue. Il s'agit de l'échappement de chaînes dans leur ensemble via les guillemets, ainsi toute chaîne placée entre guillemets peut comporter n'importe quels caractères spéciaux sans que cela ne pose de problème. Si l'on reprend le script 97 et qu'on le modifie de la manière suivante (script 98), on obtient un script sûr.

Script 98

1. @echo off

3. set /p foo=Entrez une chaîne :

4. echo.

5. echo Vous avez entré : "%foo%"

6. echo.

8. pause

Pour en revenir à ce qui avait été dit au chapitre II sur l'initialisation de variable, il est possible d'utiliser la syntaxe de la commande set pour pouvoir travailler avec une variable qui risque de comporter une injection en plaçant les guillemets dans la variable. Cependant la variable contiendra toujours les guillemets. Exemple avec le script 99 :

Script 99

1. @echo off

3. set /p foo=Entrez une chaîne :

4. set foo="%foo%"

5. echo.

6. echo Vous avez entré : %foo%

7. echo.

9. pause

Il est aussi possible d'utiliser la syntaxe des conditions pour tester si la saisie comporte un caractère qui pourrait poser problème et sortir du processus sans autres formes de procès. Exemple avec le script 100 :

Script 100

1. @echo off

2. setlocal enabledelayedexpansion

3. set "ScriptName=%~dpnx0"

4. set /p foo=Entrez une chaîne:

5. for /l %%a in (0 1 4096) do (

6. if "!foo:~%%a,1!"=="&" (

7. call :Injection "!foo:~%%a,1!" "%foo%"

8. ) else if "!foo:~%%a,1!"=="|" (

9. call :Injection "!foo:~%%a,1!" "%foo%"

10. ) else if "!foo:~%%a,1!"=="<" (

11. call :Injection "!foo:~%%a,1!" "%foo%"

12. ) else if "!foo:~%%a,1!"==">" (

13. call :Injection "!foo:~%%a,1!" "%foo%"

14. )

15. )

16. echo.

17. echo Vous avez entré : %foo%

18. echo.

19. pause

20. exit /b 0

21. :Injection

22. 1>>log.txt echo Le caractère %1 a été détecté dans une saisie utilisateur dans le script "%ScriptName%" exécuté par l'utilisateur "%UserName%". La saisie était %2.

23. exit

Dans le script 100, un message a été écrit dans un log ; cela aurait tout aussi pu être l'envoi d'un mail à l'administrateur, la prise d'une photo avec la webcam ou toute autre solution jugée nécessaire.

De même que lors d'une saisie utilisateur, la lecture d'un fichier texte peut poser problème. La commande type et la boucle for ne sont pas sensibles à l'injection. Cependant, l'utilisation ultérieure des données lues dans le fichier peut permettre l'injection. Exemple avec le script 101 et le fichier test_injection.txt :

Script 101

1. @echo off

2. setlocal enabledelayedexpansion

4. for /f "delims=" %%a in (test_injection.txt) do (

5. set str=%%a

6. echo Dans la boucle: !str!

7. call :Label "!str!"

8. )

10. pause

11. exit /b 0

13. :Label

14. echo Après un appel: %~1

15. goto :eof

test_injection.txt

1. foo & echo foobar

2. bar

On remarque que l'utilisation de la variable !str! dans la boucle ne pose pas de problème tandis qu'après l'appel du :Label, elle provoque l'injection. C'est dû au fait que la chaîne est passée au contexte du label par copie et que dans le contexte du corps de for, l'échappement se fait automatiquement. Une nouvelle chaîne étant créée pour le contexte du label, celui-ci ne considère pas la chaîne comme comportant un échappement. Si un échappement est ajouté dans le fichier texte, cela ne change rien, et ce quel qu'en soit le nombre. La seule solution est de tout traiter dans le corps de for lorsqu'une entrée provient d'un fichier texte, d'utiliser les guillemets lors d'une commande d'appel ayant en paramètre une entrée provenant d'un fichier texte et éventuellement de tester la présence de caractère de redirection (seulement s'il n'y a pas de légitimité à ces caractères).

Annexe A. Les caractères

Cette annexe présente des informations complémentaires sur l'utilisation des caractères.

A.1. Affichage des caractères de contrôle

Ces caractères graphiques sont utilisés pour afficher les points de code normalement attribués à des caractères de contrôles. Ces caractères sont applicables à tous les codes pages.

Point de code décimal

Point de code hexadécimal

Point de code Unicode

Caractères
graphiques

Description

0

0x00

U+0020

' '

Caractère espace.

1

0x01

U+263A

☺

Émoticône face souriante transparente.

2

0x02

U+263B

☻

Émoticône face souriante pleine.

3

0x03

U+2665

♥

Symbole cœur plein.

4

0x04

U+2666

♦

Symbole carreau plein.

5

0x05

U+2663

♣

Symbole trèfle plein.

6

0x06

U+2660

♠

Symbole pique plein.

7

0x07

U+2022



Symbole Puce.

8

0x08

U+25D8

◘

Symbole rectangle plein avec puce évidée.

9

0x09

U+25CB

○

Symbole cercle.

10

0x0A

U+25D9

◙

Symbole rectangle plein avec cercle évidé.

11

0x0B

U+2642

♂

Symbole mâle.

12

0x0C

U+2640

♀

Symbole femelle.

13

0x0D

U+266A

♪

Symbole note de musique noire.

14

0x0E

U+266B

♫

Symbole note de musique double noire.

15

0x0F

U+263C

☼

Symbole soleil.

16

0x10

U+25BA

►

Symbole triangle plein vers la droite.

17

0x11

U+25C4

◄

Symbole triangle plein vers la gauche.

18

0x12

U+2195

↕

Symbole flèche verticale avec double pointe (vers le haut et le bas).

19

0x13

U+203C

‼

Double point d'exclamation.

20

0x14

U+00B6

¶

Symbole paragraphe alternatif.

21

0x15

U+00A7

§

Symbole paragraphe.

22

0x16

U+25AC

▬

Symbole rectangle plein horizontal.

23

0x17

U+21A8

↨

Symbole flèche verticale avec pointe vers le haut et base en bas.

24

0x18

U+2191

↑

Symbole flèche verticale vers le haut.

25

0x19

U+2193

↓

Symbole flèche verticale vers le bas.

26

0x1A

U+2192

->

Symbole flèche horizontale vers la droite.

27

0x1B

U+2190

←

Symbole flèche horizontale vers la gauche.

28

0x1C

U+221F

∟

Symbole coin inférieur gauche.

29

0x1D

U+2194

↔

Symbole flèche horizontale avec double pointe (vers la gauche et la droite).

30

0x1E

U+25B2

▲

Symbole triangle plein vers le haut.

31

0x1F

U+25BC

▼

Symbole triangle plein vers le bas.

127

0x7F

U+2302

⌂

Symbole maison.

A.2. Insertion de caractères de contrôle et de signaux

L'interpréteur autorise l'insertion de caractères de contrôle via le clavier à différentes fins. Des signaux peuvent aussi être envoyés à l'interpréteur pour permettre une interaction complète avec l'utilisateur. Les caractères sont indiqués entre <> et les signaux sont préfixés par #.

Caractère / Signal

Raccourci clavier

Description

<SOH>

CTRL-A

Insertion du caractère SOH

<STX>

CTRL-B

Insertion du caractère STX

#SIGINT

CTRL-C

Émission du signal #SIGINT (sortie forcée du contexte courant)

<EOT>

CTRL-D

Insertion du caractère EOT

<ENQ>

CTRL-E

Insertion du caractère ENQ

<ACK>

CTRL-F

Insertion du caractère ACK

<BEL>

CTRL-G

Insertion du caractère BEL (émission d'un bip sonore par la carte mère via le buzzer)

<BS>

CTRL-H

Insertion du caractère BS (décrémente la position du curseur, limité à la ligne courante, voir exemple d'utilisation du caractère BS)

<TAB>

CTRL-I

Insertion du caractère TAB (insertion d'une tabulation)

N.C.

CTRL-J

N.C.

<VT>

CTRL-K

Insertion du caractère VT

<DEL>

CTRL-L

Insertion du caractère DEL

<CRLF>

CTRL-M

Insertion des caractères CR et LF (insertion d'un retour à la ligne formaté Windows)

<SO>

CTRL-N

Insertion du caractère SO

<SI>

CTRL-O

Insertion du caractère SI

<DLE>

CTRL-P

Insertion du caractère DLE

<DC1>

CTRL-Q

Insertion du caractère DC1

<DC2>

CTRL-R

Insertion du caractère DC2

<SUB>

CTRL-S

Insertion du caractère SUB

<DC4>

CTRL-T

Insertion du caractère DC4

<NAK>

CTRL-U

Insertion du caractère NAK

<SYN>

CTRL-V

Insertion du caractère SYN

<ETB>

CTRL-W

Insertion du caractère ETB

<CAN>

CTRL-X

Insertion du caractère CAN

<EM>

CTRL-Y

Insertion du caractère EM

#SIGTERM

CTRL-Z

Émission du signal #SIGTERM (sortie normale du contexte courant)

<NUL>

CTRL-@

Insertion du caractère NULL.

<ESC>

CTRL-^

Insertion du caractère ESC

<GS>

CTRL-$

Insertion du caractère GS

<FS>

CTRL-*

Insertion du caractère FS

#SIGBREAK

CTRL-PAUSE

Émission du signal #SIGINT dans le processus exécuté en tâche de fond.