Les batch sous Windows

Le langage batch Windows est méconnu et dénigré. Il comporte certes certains inconvénients, héritage de son lourd passé, et qui à mon sens se concentrent sur les difficultés rencontrées dans le traitement de chaines de caractères comportant les caractères réservés du langage (surtout % et ! qui sont très difficiles à échapper).

Pour autant, il reste le premier des langages de script et remplit 99% des fonctionnalités de .JS, .VBS, ou le fameux Bash Linux, oui je sais c'est génial l'open source, c'est plein de gens heureux de faire dix fois moins bien que le payant qui n'est déjà pas brillant, mais bon là au moins on sait qu'on va avoir du n'importe quoi développé par n'importe qui LOL

Les bases

Un fichier batch est donc un script généralement interprêté par l'exécutable cmd.exe qui, s'il n'est pas lancé depuis une console windows, en ouvre une automatiquement.

Dès lors le batch a accès aux fonctionnalités de cette console, pour tout ce qui est de l'aspect graphique  texte LOL mais, plus intéressant pour l'aspect flux. Car ne nous y trompons pas, nous avons à faire à un ETL!

Tout cet enfumage pour dire qu'à la base un batch ça sert surtout à exécuter des commandes, à lire les flux résultants et à les renvoyer vers d'autres commandes ou, à court d'idée, directement dans la console. Pour ce faire retenir la syntaxe suivante:

REM envoyer le flux dans un fichier en l'écrasant le cas échéant
commande > fichier.txt

REM envoyer le flux dans un fichier au cul des données existantes
commande >> fichier.txt

Bon c'est bien gentil mais un peu léger pour un ETL, on est obligé d'écrire dans un fichier? ... Meuh non

REM refiler le flux à une autre commande
commande | autre_commande

REM envoyer le flux dans un trou noir
commande > nul

REM envoyer le flux dans la console (simple!)
commande

Ouais ok, et dans l'autre sens? ... du trou noir il ne sort rien, pour le reste:

REM créer un flux à partir d'un fichier
type fichier.txt | commande
REM ou
commande < fichier.txt

REM créer un flux de toute pièce
echo ceci est mon flux | commande

REM tout supprimer sans confirmation
echo o | del *.*

Remarque subtile: les opérateurs de redirection de flux ne redirigent que le flux dit std. Si vous avez la chance d'utiliser des commandes bien développées, celles-ci remontent les erreurs éventuelles dans le flux d'erreur, qui lui reste le flux standard de l'appelant, en l'occurrence la console, sauf s'il est lui-même appelé par une commande au dessus qui elle redirige sa sortie dans un fichier... pertinent la remarque, non ?

Pour en finir, les commandes de base:

Voilà pour les bases. Ensuite, reste à voir qu'autour de ces fonctionnalités un vrai langage a été développé. Il n'est pas case sensitive, il est constitué d'une suite de commandes, séparées par un retour charriot ou le caractère & et il peut gérer les variables, les boucles, les fonctions... bref que du bonheur!

Entrées / sorties

Sorties

Tout d'abord on va donner un nom à la fenêtre d'exécution du batch avec la commande title. Ensuite on définit la taille de la fenêtre avec la commande mode con. Enfin on choisit la couleur du texte et du fond avec la commande color.

@echo off
Title Mon premier batch
mode con cols=70 lines=500
color 1E

Pour passer à la ligne, on ne peut pas faire echo seul, il faut écrire par exemple echo. Pour dessiner des tableaux, on se reporte aux caractères spéciaux (cf. plus bas)

Les caractères graphiques

Il est possible de dessiner des jolis cadres en batchs en utilisant les caractères prévus à cet effet:

Cela permet de dessiner le menu principal de notre batch, ici dans une fonction qui s'appelle donc ici par la commande call :header (voir plus loin):

:header
cls
echo ³
echo ³ %username%
echo ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
echo                                                            %date%
echo.
echo                 ÉÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ»
echo                 º   Mon premier batch   v 1.0  º
echo                 ÈÍËÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍËͼ
echo                   º (c) Ludovic de Jouvencel º
echo                   ÈÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍͼ
echo.
goto :EOF

Entrées

Bien sûr, le batch est interactif, il est donc prévu une commande qui permet à l'utilisateur de répondre à une question, ou d'entrer des paramètres. C'est en fait la commande set mais avec le qualificateur /P comme dans l'exemple ci-dessous.

echo.
set choix=
set /P choix=Entrez (o/n) : 
if /i /%choix%/==/o/ exit

Les variables

Tout d'abord nous devons activer l'évaluation retardée car cela permet de faire de l'introspection, bref des trucs super comme des tableaux. C'est la commande SetLocal EnableDelayedExpansion qui doit se trouver en début de batch.

Pour créer une variable on utilise la commande set suivie du nom de la variable, et précédée du qualificateur /a lorsqu'on souhaite faire des opérations arithmétiques dessus. Ensuite on initialise la variable, mais on peut l'initialiser à vide aussi.

Pour lire le contenu d'une variable, on l'encadre avec les symboles %. Si l'on veut faire du second degré, lire le contenu d'une variable dont le nom est lui même une expression du contenu d'une autre variable, on encadre le tout du symbole ! ce qui permet de faire des tableaux associatifs, finger in the noose:

SetLocal EnableDelayedExpansion
set rien=
set /a i=1
set TAB_1=element1
set TAB_2=element2
set TAB_3=element3
set j=1
set item=!TAB_%j%!
set /a i=%j%+1
set /a i+=1

Les manipulations de texte

Le langage permet beaucoup de choses mais attention aux caractères spéciaux qui produisent facilement des erreurs, donc tester ces choses toujours sans echo off pour bien comprendre ce qui se passe.

La règle est de faire en sorte que les chaines affectées aux variables soient toujours entre guillemets, cela evite 99% des problèmes potentiels. Sinon l'interprêteur va interprêter les &, les <, les > ou encore les | comme des commandes et c'est le plantage assuré!

Extraction de chaine

Il est très simple d'extraire des caractères contigus en spécifiant l'indice du premier caractère à extraire et le nombre de caractères à extraire. Astuce: si l'indice du caractère est négatif, on part de la fin de la chaine (-1: dernier caractère, -2: avant dernier caractère). Astuce bis: si le nombre de caractères à extraire est négatif il représente l'indice du dernier caractère en partant de la fin. Très pratique donc...

REM Premier caractere
set first=%str:~0,1%

REM Trois caracteres a partir du deuxieme
set trigramme=%str:~1,3%

REM Dernier caractere
set last=%str:~-1,1%

REM Deux derniers caracteres
set extension=%str:~-2,2%
...

Remplacement de chaine

On peut tout aussi facilement travailler sur la chaine et modifier certaines parties en remplaçant une chaine par une autre:

REM on va remplacer azr par rien
set txt=hello worazrld
echo %txt:azr=%

Les tests & contrôle de flux

Il est possible de créer des étiquettes qui commencent par le symbole : et se poursuivent par une chaine alphanumérique. Il est ensuite possible de s'y rendre par saut direct goto ou par appel de fonction call

echo.
set choix=
set /P choix=Entrez (o/n) : 
if /i /%choix%/==/n/ goto :fin
echo Vous avez entre oui
exit
:fin
echo Vous avez entre non

Les tests s'écrivent en une ligne selon if condition commande ou sur plusieurs lignes selon

if condition (
commande 1
commande 2
...
commande n)

Les opérateurs de comparaison disponibles:

Le test est rendu insensitif avec le préfix /i

:getmaxdate
REM cette fonction est appelee sur chaque ligne d'un flux
set line=%~1
REM on extrait une date du flux
set linedate=%line:~6,4%%line:~3,2%%line:~0,2%
REM on cherche la date la plus récente
if /%maxd%/ LSS /%linedate%/ (set maxd=%linedate%)
goto :EOF

Il est aussi possible de déterminer si une variable est définie:

if not defined i set i=0

Les boucles

Il existe différents types de boucles. On peut parcourir des éléments spécifiés, faire varier un compteur d'une valeur initialie jusqu'à une valeur finale avec un incrément constant, ou encore parser les lignes d'un fichier ou du résultat d'une commande.

La variable de boucle possède une nomenclature particulière préfixée par %% ce qui interdit les manipulations de chaine directement dessus. Il est par ailleurs nécessaire d'utiliser le symbole ! pour accèder aux variables à l'intérieur d'une boucle comme on le voit dans les exemples ci-après.

(il est par conséquent souvent nécessaire d'appeler une fonction depuis une boucle plutot que de tenter de faire le traitement sur place, pour des raisons de limitations syntaxiques, oui ce langage est loin d'être parfait LOL)

Ici on veut archiver des dossiers spécifiés, et on veut mettre leurs noms dans un tableau associatif (un vaste choix de séparateur est géré en natif, par exemple , ou ; ou simplement  :

set ARCHIVES=Contacts,Favoris,SitesWeb,Programmation
set /a i=1 & for %%a in (%ARCHIVES%) DO (set SEL!i!=1& set /a i=!i!+1)

Ici on fait donc varier un compteur de 1 à n et on affiche un texte associé:

for /L %%i in (1,1,%n%) Do (
set j= %%i
set text=!LIBELLE_%%i!
echo !j:~-2! ³ !text!
)

Ici on va lancer une commande pour lire les fichiers du répertoire et les stocker dans un tableau FILE_xxx, simplement avec une boucle sur la commande dir /b /ad /on.

set /a i=0 & for /f "delims=" %%p In ('dir /b /ad /on') Do (
set FILE_!i!=%%p
set /a i+=1
)

Ici on utilise les fonctionnalités de parsing pour chercher le 4ème tokens de la ligne du fichier browse.htm qui contient la chaine form_build_id et on appelle une fonction chargée d'extraire la valeur de la variable:

FOR /f "useback tokens=4" %%a in (`findstr "form_build_id" "%TEMP%\browse.htm"`) do call :getformid "%%a"

Remarque: le mot réservé useback permet d'utiliser le symbole ` et donc d'encapsuler les quotes dans la commande findstr "form_build_id" "%TEMP%\browse.htm". Oui, toujours des rustines pour gérer la mauvaise gestion de l'échappement inhérente au langage...

On peut aussi spécifier un délimiteur de fin de ligne avec eol ou encore ignorer un certain nombre de lignes avec skip, et enfin parcourir tout un fichier très simplement:

REM Liste des utilisateurs
for /f "eol=; skip=1 tokens=1 delims=," %%a in (data.dat) do echo %%a

Les fonctions

On peut définir des fonctions et leur passer des paramètres, on peut même faire de la récursivité en utilisant les commandes SETLOCAL et ENDLOCAL en début et fin de fonction.

Par exemple, ici, on va souligner un texte passé en paramètre. Le texte est passé entre guillemets pour autoriser les espaces, et éviter au maximum les problèmes d'interprêtation liés à l'utilisation éventuelle de caractères réservés dans la chaine. Donc la boucle ci-dessous s'assure que count contient toujours du texte entre quotes. (pénibilité du langage):

:underline
SET count=%*
SET line=ÄÄ
:undloop
if %count%=="" ( echo %line%& GOTO :EOF )
SET count="%count:~1,-2%"
SET line=%line%Ä
GOTO undloop
GOTO :EOF

Noter le GOTO :EOF qui permet de retourner à l'appelant, et l'utilisation du symbole %* qui renvoie tous les paramètres, pour un paramètre en particulier, on utilise classiquement %1, %2, ... ou bien si l'on souhaite supprimer les guillemets éventuels dans ces paramètres %~1, %~2, ...

Il est à souligner que le batch entier peut être appelé de l'extérieur et constitue donc une fonction. La sortie du batch retourne à l'appelant. Il est alors possible de renvoyer un code d'erreur (0 = pas d'erreur) susceptible d'être lu dans la variable %errorlevel%:

:ok
exit /B 0

:failed
exit /B 1

Les variables systèmes

Tout d'abord les chemins d'accès, ce qui est excessivement utile, notamment le dossier temporaire, le dossier des applications et celui du profil utilisateur:

Les inmanquables:

Quelques informations sur la session:

Il y a aussi une foule d'informations sur le système:

Les commandes systèmes

Nativement le langage batch incorpore des instructions de gestion de fichiers.

if exist filename ...
if not exist filename ...

if exist directory ...
if not exist directory ...

rem se déplace dans un dossier (aller simple)
cd directory
rem se déplacer sur un autre lecteur (aller simple)
cd /d directory
rem se déplace dans un autre dossier en mémorisant le parent
pushd directory
rem retour au parent
popd

Un certain nombre de commandes externes sont par ailleurs disponibles nativement sous Windows.

Souvent utiles, téléchargeables gratuitement: