Site HS -> nouveau site en ligne

Bonjour,

J’annonce la fermeture de ce site pour l’ouverture du nouveau :

http://hogren.uphero.com

 

J’ai créé un article dessus y expliquant les raisons:

http://hogren.uphero.com/2015/12/26/changement-dhebergeur/

 

Merci à tous !

Advertisements

Getopt or Getopts, that is the question

Bonjour à tous,

Qu’est-ce que getopts ?
Est-ce mieux que getopt ?

Lors de ma lecture de l’«Advanced Bash Scripting Guide», je suis tombé sur la fonction «builtin» nommée «getopts». Pour information, une fonction dite «builtin» est une fonction intégrée à Bash. Elle n’est pas appelée par Bash mais est directement intégrée dans son code. Ça a l’avantage d’accélerer son execution.

À côté de ça, il y a la fonction «getopt» (sans le «s»). Il me fallait donc comparer les deux pour voir réellement de quoi il en retourne. Les informations fournies par mon bouquin (PDF) ne me suffisaient pas.

 

Que fait «getopt» ?

C’est simple, elle analyse une chaîne de caractère, habituellement la liste des arguments du script, pour placer en tête les options reconnues.

Exemple: on donne en arguments à notre programme les options -a -b -c -d:

$ set — “-a” “-b” “-c” “-d”

On dit à «getopt» que le script peut reconnaître les options «-a» et «-d»:

$ getopt “ad” “$@”
getopt: unknown option — b
getopt: unknown option — c
-a -d —

La ligne finale est sur la sortie standard (fd 1). Les deux premières sont sur la sortie d’erreurs (fd 2).
Nous voyons que «getopt» a gardé «-a» et «-d» et a rejeté les autres. Le «–» marque une séparation entre les options commençant par un tirêt «-» et reconnues, et les options sans tirêt.

Exemple:

$ set — “-a” “-b” “-c” “-d” “e”
$ getopt “ad” “$@”
getopt: unknown option — b
getopt: unknown option — c
-a -d — e

Il est possible d’ajouter des options avec valeurs comme «-n 5». Il faut dans ce cas, se servir des deux points «:». Il faut, dans la chaîne correspondante aux options reconnues, faire succéder à une option, ces deux points, si cette dernière nécessite un argument supplémentaire. «Getopt» se chargera alors de correctement la faire ressortir dans la chaîne de sortie.

Exemple:

$ set — “-a” “5” “-b” “c” “-d” “e”
$ getopt “a:d” “$@”
getopt: unknown option — b
-a 5 -d — c e

Un avantage à «getopt» par rapport à «getopts» est qu’il supporte les options longues. Mes exemples précédents montrent des options sous la forme d’un tirêt suivi d’une lettre. Les options de ce type sont appelées options courtes. Une option longue est une option commençant par deux tirêt «–» suivi de plusieurs lettres. Certains caractères spéciaux sont acceptés comme le tirêt. «Getopt» à un paramêtre précis à ajouter pour accepter ce genre d’options. Ce paramêtre est «-l» suivi d’une chaîne de caractères contenant les options longues devant être reconnues, sans les doubles tirêt, et séparées entre elles par des virgules.

Exemple:

$ set — “-a” “–long-option” “–second-long-option”
$ getopt -l “long-option,second-long-option” “a” “$@”
-a –long-option –second-long-option —

Si vous avez une valeur à placer après une option, il faut, comme précédemment utiliser les deux points «:».

Exemple:

$ set — “-a” “–long-option” “valeur” “–second-long-option”

$ getopt -l “long-option:,second-long-option” “a” “$@”
-a –long-option ‘valeur’ –second-long-option —

Voilà les bases sur le fonctionnement de la fonction «getopt». Vous devinez bien qu’il faut ensuite travailler avec la sortie de la commande pour se servir des options filtrée par «getopt».
Le meilleur choix est ici, générallement, une boucle «while» incluant elle même un «case…esac» (plus communément appelé «switch…case» dans d’autres langages).

Exemple:

Si vous n’avez que des options courtes, sans valeur. Le code suivant vous suffira.

$ set — “-a” “-b” “-c” “-d”
$ set — $(getopt “ad” “$@”)
getopt: unknown option — b
getopt: unknown option — c
$ while [ ! -z “$1” ]; do {
> case “$1″ in
> -a) declare a=”yes”;;
> -d) declare d=”yes”;;
> *) break;;
> esac;
> shift;
> }; done;
$ echo $a
yes
$ echo $b
yes

Selon ce dont vous avez besoin, il sera nécessaire de complexifier ce précédent exemple. Je ne vais pas m’attarder dessus, car je vais vous montrer les avantages de «getopts».

 

Comment fonctionne «getopts» ?

«Getopts» ne s’appelle pas exactement de la même façon que «getopt». «Getopts» se base sur les arguments du script, ou de la fonction en cours d’execution. Cette information n’apparaît donc plus à l’appel de la commande. La chaîne contenant les options reconnues est la même que celles des options courtes pour «getopt». Il est judicieux de mettre deux points «:» au début de cette chaîne pour activer le mode silencieux et que «getopts» n’affiche pas les erreurs d’options non reconnues et autres. Le second argument important à placer est le nom de la variable que l’on souhaite utiliser pour récuperer une à une les options.

À chaque exécution de la commande «getopts», cette dernière va passer d’un argument au suivant dans la liste des arguments du script («$@»). Si l’option est reconnue, elle est inscrite dans la variable donnée en paramêtre, sinon, un point d’interrogation est placé dans cette même variable. Une fois tous les paramêtres parcourrus, un point d’interrogation est placé dans la variable, et la commande renvoie un code d’erreur «1».

Si un paramêtre doit recevoir un argument («-n 3»), il faut utiliser, comme précédemment les deux points «:» dans la chaîne de recherche. Ensuite, quand la commande «getopts» tombe sur ce type d’options, l’argument en question passe dans la variable $OPTARG.

Au final, il faudra, comme pour la commande précédente, se servir judicieusement d’une boucle «while» et d’un «case…esac» pour extraire les options et s’en servir. Néanmoins, je trouve cette seconde options plus simple à utiliser. Les options suivies d’arguments sont mieux gérées, il y a plus d’automatisation.

Exemple:

$ set — “-a” “-b” “arg” “-c” “-d”
$ while getopts “:ab:d” OPTNAME; do {
> case $OPTNAME in
> a) echo “a”;;
> b) echo “l’option $OPTNAME a pour argument $OPTARG”;;
> d) echo “l’option $OPTNAME est en position $OPTIND”;;
> esac;
> }; done;
a
l’option b a pour argument arg
l’option d est en position 6

J’ai utilisé dans cet exemple la variable $OPTIND. C’est une variable qui est par défaut à 1 quand on démarre Bash. «Getopts» s’en sert comme index pour se repérer dans la liste des arguments. Nous pouvons l’utiliser pour connaître la position de l’argument en cours d’analyse.

Pour conclure, je dirais qu’il est préférable d’utiliser «getopts» pour sa simplicité d’utilisation, sa flexibilité, et pour le fait qu’il soit «builtin». Le seul problème est qu’il ne gère pas les options longues.

Pour aller plus loin, j’ai vu au cours de mes recherches qu’il existait des bibliothèques avancées permettant de gérer les options en Bash. Je pense notamment à Bash-Argsparse disponible sur GitHub (https://github.com/Anvil/bash-argsparse). Je testerai sans doute ça. J’en fairai aussi sans doute un autre billet.

En espérant que ce retour sur la gestion des arguments en Bash vous aura plu, n’hésitez pas à laisser vos commentaires.

À bientôt !

Remplacement de sous-chaînes dans une variable en Bash

Ma lecture du livre ABS (Advanced Bash Scripting) Guide progresse. C’est très instructif.
Je continue donc à vous fournir une présentation de mes trouvailles.

Aujourd’hui, je vais vous montrer la fonction de substitution de texte au sein d’une variable. La syntaxe est extrêmement simple, non intuitive (par rapport aux autres langages de programmation), mais simple. La voici:

${NomVariable/chaine_recherchee/remplacement} # Remplacement unique
${NomVariable//chaine_recherchee/remplacement} # Remplacement global

Vous l’aurez compris, la première ligne est utile pour remplacer la première occurence de la chaîne «chaine_recherchee» par «remplacement». La seconde quant à elle permet de remplacer toutes les occurences de «chaine_recherchee» par «remplacement».

«chaine_recherchee» peut être une chaîne de caractère entourée ou non par des guillemets. Dans ce cas, faites attention aux caractères spéciaux qui pourraient être interprétés par Bash et donner un rendu inattendu. Le mieux est de l’entourer de guillemets, et même d’apostrophes si c’est possible (voir mon billet https://laisselasouris.wordpress.com/2015/05/20/bien-gerer-les-caracteres-speciaux-en-bash/). Elle peut aussi n’être qu’un nom de variable, précédé de l’habituel «$» (dollar).

«Remplacement» possède les mêmes possibilités que «chaine_recherchee». Elle peut être également vide, ce qui revient à supprimer «chaine_recherchee». Bash à une, même plusieurs autres fonctions pour la suppression de sous-chaînes, mais chacun ses préférences syntaxiques.

J’ai trouvé cette fonction utile pour modifier des variables d’environnement telles que $PATH, $CFLAGS. Quand vous faites de la compilation avec les outils GNU (gcc, make, etc.) par exemple, cela peut s’avérer très utile, et vous faire gagner du temps.

Un petit exemple:

echo $PATH
# /home/hogren/bon:/usr/local/bin:/bin:/usr/bin/
# Oh non, une faute de frappe s’est glissée.
PATH=${PATH/bon/bin/}
echo $PATH
# /home/hogren/bin:/usr/local/bin:/bin:/usr/bin/

Si vous êtes intéressés par la manipulation de chaînes de caractères, je vous invite à lire le chapitre 10 du livre ABS Guide.

N’hésitez surtout pas à me laisser des commentaires si vous avez une question, une remarque, ou autre.

À Bientôt pour un prochain billet.

plannifier une action unique

Voici un petit billet sur une commande très pratique. Beaucoup d’entre vous connaissent certainement CRON (mcron, vcron, peu importe), une sorte de gestionnaire de tâches plannifiées. Cet outil est très utile pour les tâches répétitives. Mais qu’en est-il des tâches à ne plannifier qu’une fois ? Par exemple, suite à une grosse mise à jour du noyau, d’Apache, ou que sais-je, il est nécessaire de redémarrer le service en question, ou carrément le serveur. Mais vous n’allez pas le faire pendant les heures de fréquentation. Vous allez programmer ça pour 2:00, 3:00 du matin, ou pour le week-end.

Éditer une ligne dans CRON pour ensuite la supprimer fonctionnerait, mais ce n’est pas très fonctionnel.

C’est pourquoi il existe la commande «at», qui est, sauf erreur de ma part, native dans toutes les distributions actuelles (Gnu/Linux, sans doute Gnu/BSD, mais pas cygwin). Je vais vous montrer la manière que je pense être la plus simple de l’utiliser.

Étape 1:
Mettre toutes les commandes à exécuter dans un fichier Bash (.sh).
Vous pouvez mettre ce fichier dans /tmp/, il sera supprimé au prochain redémarrage.

Étape 2:
Entrez une commande ressemblant à la suivante.

at -f /chemin/fichier/commandes.sh  -t MMJJhhmm

Le -f (f pour «file») permet d’indiquer le fichier de commandes.
Le -t (t pour «time») permet d’indiquer la date et l’heure. Si le mois est avant octobre, il faut rajouter le 0 devant pour que ça tienne sur deux chiffres, et idem pour le jour, l’heure, et la minute. Il est possible de rajouter les années, les secondes (ce qui ne fonctionne pas sur la Debian qui m’a servi pour les tests). Pour des détails, je vous invite à regarder le manuel («man at»).

Exemple concrêt d’une commande:

at -f /tmp/commands.sh -t 07120010

Traduction:
Exécuter le fichier de commandes /tmp/commands.sh à 00:10 le 12 juillet.

Voilà pour ce court tutoriel. Si vous avez des remarques, des questions, ou autres, n’hésitez surtout pas, laissez un commentaire.

Bien gérer les caractères spéciaux en Bash

Bonjour à tous,

Il y a peu, je vous faisais tout un comparatif entre le Batch et le Bash. Pour me perfectionner en Bash, ce langage que j’adore, je me suis lancé dans la lecture du livre «Advanced Bash Scripting Guide» de Mendel Cooper, disponible librement en pdf ou en ligne. Une petite recherche sur tldp.org (un super site que je vous recommande) vous permet de le trouver facilement.

Il faut savoir que je lis plusieurs choses à la fois et donc, ma lecture avance à son rythme. J’ai décidé de vous présenter les points que je trouve intéressants. Mon premier article pour cette série va porter sur les caractères spéciaux et surtout les différentes manières de ne pas les interpréter.

Tout d’abord, qu’est-ce que l’on appelle un caractère spécial. Ici, un caractère spécial est un caractère qui peut avoir un autre sens que l’impression du caractère lui-même à l’écran, dans une variable ou autre. Prenon pour exemple le «#» il peut être utilisé pour imprimer un «#» à l’écran, mais aussi pour écrire un commentaire. Il est utilisé pour plusieurs autres fonctions, que je ne vais pas décrire ici.

Donc, maintenant, passons au trois manière d’échapper ces caracatères, ou en d’autres termes, d’annuler tout autre comportement que l’impression de ces caractères.

La première est d’utiliser le caractère «\» (barre oblique inversée, ou anti-slash, ou backslash) avant le caractère spécial. Exemple:

echo \#

Renvoie:

#

Étant donné que «\» est aussi un caractère spécial, il faut pensez à l’«échapper» si vous voulez afficher ce caracatère. Cela donne:

echo \\

Qui renvoie :

\

La seconde méthode d’échappement de caractères est d’utiliser les «”» (guillemets, ou double quote) en encadrement. Exemple :

echo “Le caractere # permet d’ecrire un commentaire.”

Renvoie:

Le caractere # permet d’ecrire un commentaire.

Attention, cette méthode n’oublie pas la totalité des caractères spéciaux. C’est pour cela qu’elle est appelée «partial quoting». À l’intérieur de ces guillemêts, il y a trois choses importantes qui peuvent être interprétées (si jamais vous en voyez d’autres, n’hésitez pas à me laisser un commentaire, je rectifirais si nécessaire):

– Le guillemêt «”» lui-même qui permet de définir le début et la fin.
– Le «\» qui permet d’échapper un caractère. Exemple:

echo “Ceci est un guillemet: \” ”

Renvoie:

Ceci est un guillemet: ”

– Tout ce qui est appelé avec le caractère «$» (dollar). Cela comprend les variables mais aussi toutes les fonctions avancées de remplacement, substitution, arithmétie, etc. Dans ce cadre précis, les «{», «}», «(», «)», «[», «]», «:», «#», «%», «/», et j’en passe, agissent normalement, sans se soucier qu’ils soient «quotés». Voici un exemple:

var1=”Je kiffe bash.”;
echo “{ (( [[ ${var1/bash/Bash} ]] )) }”

Renvoie:

{ (( [[ Je kiffe Bash. ]] )) }

Notez le «B» majuscule.

La troisième et dernière méthode d’échappement est similaire à la précédente mais utilise des «’» (apostrophe, ou single quote). Celle-ci est appelée «full quoting» car elle n’interprête plus rien (à part le «’» lui-même pour connaître le début et la fin tout de même). Voilà ce que ça donne:

var1=”Je kiffe bash.”;
echo ‘{ (( [[ ${var1/bash/Bash} ]] )) }’

Renvoie:

{ (( [[ ${var1/bash/Bash} ]] )) }

Une petite astuce:
La commande «echo» lit les ses arguments les un à la suite des autres. Il concatène le tout. On peut ainsi mixer entre «’» et «”» sur une même ligne de commande:

echo -e ‘Caractere dollar: $.'”\n”‘Caractere apostrophe: ‘”‘”‘.'”\n”‘Caractere backslash: \.’

Renvoie:

Caractere dollar: $.
Caractere apostrophe: ‘.
Caractere backslash: \.

Voilà. C’est la fin de mes petites explications. Je pense que comprendre ces différents moyens d’interprétation ou non de caractères peut faire économiser pas mal temps en création et débogage de scripts.

N’hésitez pas à me laisser un commentaire. Dites moi si vous trouvez le «cours» utile, pas utile, bien écrit, bourré de fautes, etc., etc.

Bash VS Batch ou Gnu VS Windows

Bonjour à tous,

Je vais essayé de faire ici un comparatif entre le «scripting» en Batch (les «.bat», voire «.com», de Windows) et celui en Bash (univers Gnu/Linux entre autres). C’est un sujet assez complexe. En réalité ce n’est pas seulement les langages qui sont comparés, mais aussi leurs environnements. Chacun de ces deux langages est limité en soit, comme tout langage de programmation. Ils tirent ensuite partie de tous les outils mis à disposition.

La structure du langage

Petit point de détail pour commencer. Le Bash est sensible à la casse, c’est à dire qu’il différencie les minuscules des majuscules et donc qu’il n’interprétera pas de la même façon «ECHO» et «echo». Les noms de commandes étant généralement en minuscule dans le monde Gnu/Linux, vous ne pouvez utiliser des majuscules pour appelez celles-ci. La commande «Echo test» renverra une erreur indiquant que la commande «Echo» est introuvable.

Le Batch est à l’inverse, insensible à la casse. «SET VaRiAbLe=”VaRiAbLe”» équivaut à «set variable=”VaRiAbLe”» (il l’est tout de même pour ses chaînes de caractères).

Il y a des avantages dans les deux méthodes. Le Batch laisse une flexibilité au codeur, alors que le Bash préfère une certaine sûreté du code, ainsi qu’un nommage plus proche des autres langage.

Les variables

Les variables sous Windows sont vraiment très particulières. Elle sont, par défaut, remplacée à la lecture et non à l’exécution de la ligne. Cela veut dire que toutes les variables d’une ligne de texte sont remplacées avant que cette même ligne soit exécutée. Enfin, le terme «ligne de texte» utilisé dans la version française de la commande «SET /?» (l’aide) n’est pas tout à fait exacte. Il faudrait parler de commande, ou même de bloc, car un «IF», un «FOR», ou autre, sur plusieurs lignes, subit ce remplacement. Cela donne quelque chose d’assez contre-intuitif et, pour ma part, inutile. Voici un très simple exemple :

SET Nom=Anonyme
IF “%Nom%” EQU “Anonyme” (
SET Nom=Personne
ECHO %Nom%
)

Tout programmeur d’un langage lambda s’attendrait à ce que le script nous renvoie «Personne». Si vous avez bien compris ce que je vous ai décrit plus haut, vous aurez deviné que le script nous renvoie en réalité «Anonyme».

Heureusement, il y a une technique pour changer ce comportement, et heureusement elle est documentée un minimum (dans SET /?). Pour faire court, quand vous commencez un script en .bat, en en-tête de fichier, insérez la ligne suivante :

SETLOCAL ENABLEDELAYEDEXPANSION

Ensuite, n’utilisez plus les «%» (pourcentages) pour encadrer les nom de variables mais les «!» (points d’exclamation).

Passons maintenant à ce système d’encadrement de nom de variable. Dès que l’on pousse un peu au niveau du développement, on s’aperçoit qu’il est problématique. En effet, extraire une sous-chaîne d’une variable se fait en rajoutant des paramètres dans l’encadrement cité précédemment. Voici un exemple qui extrait les trois premières lettres d’une variable :

SET Nom=Anonyme
ECHO !Nom:~0,3!

L’encadrement devient problématique au moment ou vous voulez mettre des variables à la place du «0» et/ou du «3». Par exemple, je veux diviser un mot en un groupe de lettres, d’une longueur configurable :

SET variable=0123456789
SET taille=3
SET debut=0
ECHO !variable:~!debut!,!taille!!

Cela renvoie :

0123456789debuttaille

Même en essayant de mixer les deux méthodes d’encadrement (ce qui n’est toujours possible), comme ceci :

ECHO %variable:~!debut!,!taille!%
ECHO !variable:~%debut%,%taille%!

La première renvoie :

variable:~0,3

La seconde renvoie bien :

012

Cependant dans une situation telle que celle-ci, il est beaucoup plus fréquent que ce soit la variable «debut» qui soit remplacée à chaque itération de boucle, que la chaîne «variable». Cette forme est donc peu utile, et il y a un gros manque pour pallier à la première erreur. Étrangement, les itérateurs sont bien dynamiquement remplacer, à chaque itération de boucle, bien qu’ils soient écrits avec deux «%» (syntaxe particulière, voir plus bas).

Je n’ai pas terminé mon tour d’horizon sur les variables Batch, mais pour que vous commenciez déjà à pouvoir faire une comparaison avec le variables en Bash, Je vais un peu vous présenter ces dernières.

En Bash, les variables sont précédés d’un «$» (dollar). Afin de supprimer toute ambiguïté possible, lors par exemple de concaténation avec la commande echo, nous pouvons encadrer le nom des variables par des «{}» (accolades). Voici un simple exemple :

variable=”normal”
echo elle est ${variable}e

Ensuite, en Bash, une sous-chaîne se fait de façon assez similaire que le Batch. Voici un exemple renvoyant les trois premiers caractères d’une variable :

echo ${variable:0:3}

Il est très facile d’imaginer quelle sera le code pour extraire une sous-chaîne dont le point de départ et la longueur sont des variables :

debut=0
longueur=3
echo ${variable:$debut:$longueur}

Vous l’aurez compris, en Bash, il n’y a pas cette notion de remplacement à la lecture ou à l’exécution. Tout se fait à l’exécution, comme tout bon langage.

Une petite chose intéressante, pour obtenir la longueur d’une chaîne, il vous suffit d’utiliser le symbole «#» (dièze) ainsi que les accolades d’encadrement, comme ce qui suit :

echo ${#variable}

En Batch, cela n’est pas possible de façon simple. Il faut créer une fonction (enfin, une pseudo-fonction), une boucle ou passer par un autre langage (cscript, wscript, etc.).

Comme vous avez pu le voir dans mes exemples précédent, en Batch, vous déclarer une variable avec «SET», en Bash, il y la commande «declare», mais cette dernière est implicitement invoquée si vous l’omettez.

En Batch pour déclarer une chaîne de caractères, il ne faut pas utiliser de guillemet comme vous en avez l’habitude pour les autres langages, sinon les guillemets sont inclus dans la variable. Prenons par exemple ce code :

SET variable=”test”
ECHO !variable!

Il renverra :

“test”

En Bash, prenez l’équivalent :

variable=”test”
echo $variable

Il renverra :

test

C’est une petite nuance à connaître. En Batch, pour ce qui est des espaces à inclure dans les variables, sans inclure de guillemets, on doit encadrer la variable et son contenu par des guillemet, comme ce qui suit :

SET “variable=test test”
ECHO !variable!

Je pense avoir été assez long dans ma comparaison des chaînes de caractères. Passons maintenant aux autres types de variables.

En Batch, il n’y a pas de moyen de placer, au milieu d’une commande, une valeur calculée. Il faut au préalable mettre cette valeur calculée dans une variable. La commande à utiliser est «SET /A». La syntaxe qui suit, est encore une fois, très particulière. Il faut placer tout ce qui n’est pas un nom de variable entre des guillemets. Voici un exemple, comme à mon habitude très simple :

SET A=1
SET B=2
SET /A variable=A”+”B
ECHO !variable!

En Bash, que ce soit dans la déclaration d’une variable, ou au milieu d’une commande, écrivez la de façon la plus simple du monde, et encadrez la formule par «$((» et «))» (doubles parenthèses précédées d’un symbole dollar). Voici un exemple :

A=1
B=2
C=$(($A+$B))
echo $(($C*2))

En Batch, pour les variables, ça s’arrête là.

En Bash, vous pouvez créer des tableaux de valeurs, appelés «array» en anglais. Il suffit d’utiliser «declare -a». L’exemple qui suit crée et appelle un tableau de valeurs :

declare -a tableau
tableau[0]=”gnous”
tableau[1]=42
echo J’ai croise $tableau[1] $tableau[0].

La sortie sera :

J’ai croise 42 gnous.

En utilisant «declare -A», on peut faire des tableaux associatifs (un nom pour une valeur). Un exemple très simple :

declare -A tableau2
tableau2[“animaux”]=”manchots”
tableau2[“nombre”]=10
echo J\’ai croise ${tableau2[“nombre”]} ${tableau2[“animaux”]}.

Ce qui nous donne :

J’ai croise 10 manchots.

La substitution de commande

Un des immenses points forts de Bash est la substitution de valeur. Le principe est si simple, mais tellement efficace !

Pour faire court, et parce que cela ne demande guère plus d’explication, la substitution permet, n’importe où dans le script, d’indiquer que l’on veut utiliser la sortie d’une commande comme toute ou partie d’une commande. Voici un exemple simple. Imaginons que le nom d’utilisateur soit dans le fichier nom_utilisateur.txt et que le prenom soit dans prenom_utilisateur.txt :

echo “Bonjour “`cat prenom_utilisateur.txt nom_utilisateur.txt |sed -e “s/\n/ /g”`

Pour info, «sed -e “s/\n/ /g”» permet de remplacer les retours à la ligne par des espaces.

Cette fonction de substitution est vraiment beaucoup utilisée avec les boucles, que nous verrons plus tard. Il est possible d’utiliser la syntaxe «`commande à substituer`», avec des «`» (apostrophe oblique) ou «$(commande à substituer)», avec des parenthèses simples précédées d’un symbole dollar «$».

Les conditions «IF»

Dans le cas du «IF», la syntaxe est assez stricte pour les deux langages bien qu’elle diffère beaucoup entre les deux.

En Bash, l’instruction «if» est clairement divisée en trois commandes.

La première est la commande «if». Cette commande doit être suivie d’une autre commande avec ses arguments si besoin. C’est sur la valeur de sortie que va se baser la commande suivante.

La commande qui suit «if», est la plupart du temps «test». Plus exactement, on utilise généralement «[» qui est une sorte de raccourci vers cette dernière. Si vous choisissez ce raccourci, il faut terminer par «]». Il ne faut pas oublier que «[» et «]» sont pour le premier, une commande, et pour le second, un argument. Il faut donc les séparés du reste de votre ligne de commandes par des espaces.

La seconde commande de la série est «then». Elle est suivie de la ou des commandes (voir après) à exécuter si le précédent test renvoie 0.

Vous pouvez enchaîner avec une commande «elif» qui correspond à un «sinon si» et qui se comporte comme un «if» (il vous faut donc le «then» après), ou un «else» qui se comporte comme un «then» mais qui attends les instructions à lancer en cas d’échec (valeur autre que 0).

Pour finir, la commande «fi» ferme l’instruction.

Je vous ai divisé ça pour bien vous faire comprendre la syntaxe. Je vous rappelle donc qu’en Bash, les commandes sont séparés par des «;» (point-virgules) ou des retours à la ligne. Pour lister un ensemble de commandes là où l’on en attend qu’une (le «then» par exemple), il faut utiliser les «{» et «}» (accolades). Au final, cela nous donne une syntaxe à la fois stricte et flexible.

Quelques exemples :

if test 1 -eq 2; then echo “1 est egal a 2.”; else echo “1 n’est pas egal a 2.”;fi;

if [ “facile” == “rapide” ]
then {
echo “Si c’est facile, c’est rapide.”
echo “Si c’est rapide, c’est facile.”
}
elif [ “facile” == “bien” ]; then {
echo “Si c’est facile, c’est bien.”
echo “Si c’est bien, c’est facile.”
} else echo “Facile ne veut pas toujours dire rapide.”
fi

Je vous invite grandement à regarder le manuel de la commande «test», via la commande «man test».

En Batch, la philosophie est un peu différente. Il n’est pas possible d’utiliser directement une fonction de test de votre choix. Vous pouvez tester l’existence d’un fichier, comparer deux chaînes ou deux nombres. Les instructions à exécuter sont entourés par des «(» et «)» (parenthèses). La première doit obligatoirement être situé sur la même ligne que la condition et être séparée de cette dernière d’un espace (au minimum). Il y a, bien sûr, le «ELSE IF» et le «ELSE». Voici un petit exemple :

IF NOT EXIST “C:\la_verite_sur_simple.txt” (
IF “simpliste”== “simple” (
ECHO “Simple veut dire simpliste.”
) ELSE IF “simpliste”==”compliqué” (
ECHO “Simpliste veut dire compliqué.”
) ELSE ( ECHO “Quelque chose de simpliste n’est pas forcement quelque chose de simple.”
)
)

La syntaxe du Bash à quelques petits défauts comme le mot «fi», très peu utilisé dans le monde de la programmation, pour terminer une condition. Cependant elle permet une grande flexibilité d’écriture, et même d’utilisation. Vous pouvez aisément utiliser n’importe quelle commande à la place de test. Comme vu précédemment, en Batch, il faut faire attention aux guillemets. Quand vous comparez deux variable contenant chacun une chaîne de caractère, le mieux est de les entourer par des guillemets, comme ceci :

IF “!variable1!” == “!variable2!” ( ECHO “Elle sont egales.” )

Les boucles

Qu’est-ce qu’ils ont été faire avec la boucle «FOR» du côté de Batch ! Première chose, la variable d’itération ne peux être qu’une lettre. Je suis d’accord que, quand on fait du développement rapide pour un très petit programme, on utilise souvent la lettre i, mais quand le programme (ou script dans notre cas) devient conséquent, ou que nous ne sommes pas seuls à travailler dessus, il est bien utile d’utiliser des noms cohérents. Seconde chose, la syntaxe du Batch nous oblige non pas à entourer cet itérateur de «%» (pourcentages) comme pour les autres variables, mais de l’en faire précéder de deux. Troisième ignominie, le Batch est sensible à la casse pour les itérateurs, alors que, rappelons-le, il ne l’est pas pour les variables standards.

Le second point est particulièrement handicapant. Il rend l’itérateur inutilisable directement, pour par exemple en extraire une sous-chaîne. Le plus simple, est de créer une variable qui prend la valeur de l’itérateur, à chaque passe de la boucle, ce qui n’est pas très joli tout de même.

Sinon, en terme de fonctions, la boucle «FOR» de Batch nous permet de parcourir une séquence de nombres, avec un départ, une arrivée, et un pas configurables, de parcourir une liste de mots (pouvant être des noms de fichiers), un dossier avec ou non un filtre basic (C:/repertoire/a*), avec ou non de la récursivité, ou encore le contenu d’un fichier ligne par ligne et mot par mot (cf. «FOR /?»). Pour le parcours d’une liste de fichiers, nous avons accès à quelques attributs comme la date et l’heure de la dernière modification. Et pour le parcours du contenu de fichiers, nous avons accès à des options comme les délimiteurs de mots, les caractères de fin de ligne (pour ne pas afficher des commentaires après un «;» par exemple).

La syntaxe n’est vraiment pas des plus jolies. Un petit exemple :

FOR /F “tokens=1,2,*” %%i IN (fichier.txt) DO (
ECHO premier mot : %%i
ECHO second mot : %%j
ECHO reste de la ligne : %%k
)

En Bash, grâce à la substitution, les possibilité sont quasiment infinies. La syntaxe est très simple :

for nom_iterateur in `commande pour générer une liste de mots`; do commande à exécuter; done;

La variable nom_iterateur s’appelle ensuite comme toute variable, avec un «$» (dollar) devant. Il est bien sûr possible, comme pour le «if», d’utiliser les «{» et «}» (accolades) pour placer un groupe de fonctions dans le corps de la boucle.

Ne serait-ce qu’avec cette syntaxe, il vous ai possible de faire une quantité astronomique de choses, en utilisant par exemples les commandes suivantes :
cat, echo, grep, sed, ls, head, tail, seq

Mais le Bash ne s’arrête pas là. Pour les habitués du C++, JavaScript, Java, ou beaucoup d’autres langages, il vous est possible de faire quelque chose comme ceci :

for (( iterateur=0; iterateur<=100; iterateur++ )); do {
echo test $iterateur;
echo “———–“;
}; done;

Il ne faut pas oublier les espaces qui entourent les «((» et «))» (doubles parenthèses). Les boucles en Bash accèpte le «break» pour stopper une boucle, et le «continue» pour terminer une itération et passer à la suivante.

Les «switch/case»
Il n’est tout bonnement pas possible de faire un switch/case avec du Batch. Je ne vais pas m’attarder dessus, car ce n’est pas une solution que j’utilise beaucoup. Je trouve aussi simple d’utiliser des «if». Voici tout de même un exemple pour vous montrer la syntaxe en Bash :

echo “Quel est votre nom ? ”
read name
echo “Bonjour $name.”
case $name in
Bob | bob | BOB ) echo “Comment tu vas ?”;;
Cruella | cruella  ) echo -n “Je vous connais, mais je ne vous aime pas.”;;
*) echo “Ce script n’est pas fait pour vous.”;;
esac

La commande echo

Bien que très simpliste, la commande «echo» est un peu plus sympathique en Bash. Le plus important, pour moi, est que vous avez la possibilité de ne pas envoyer de retour à la ligne à la fin du texte, pour par exemple écrire une phrase en plusieurs étapes.

En Batch la commande «ECHO» sert d’une part à afficher un message, mais aussi à activer ou désactiver l’affichage des commandes avant exécution dans un script.

Le problème engendré par ceci, est que quand vous faites un simple «ECHO !VariableVide!», cela vous renvoie :

Commande ECHO activée/désactivée

En Bash, un «echo» ou «echo $VariableVide» renvoi une ligne vide.
C’est plus compréhensible, et beaucoup plus facile à déboguer.

Pour les curieux, pour afficher les commandes avant exécution dans un script Bash, il faut lancer le script avec «Bash -v» (option -v pour «verbose»).

Les «Pipelines»

Les pipelines sont, à ma grande surprise, disponible sous Batch. Je ne les ai jamais utilisés et je pense qu’il y a peu d’intérêt à les utiliser avec le jeu restreint de commandes de Batch. À l’inverse de celles de l’environnement Gnu/Linux, les commandes de bases de Windows n’ont, pour la plupart pas la possibilité de lire l’«entrée standard», sauf la commande «SET /P NomVariable» et «MORE».

Les redirections

Les deux langages dispose d’un système de redirection. La sortie est divisé en deux flux (le flux standard et le flux d’erreurs). Il y a la sortie console, qui par défaut réunit ces deux flux. Le Batch et le Bash permettent de rediriger l’un ou l’autre des flux vers un fichier. Le Bash vous permet aussi de rediriger vers un périphérique. Les deux vous permettent, de façon un peu différente, de ne pas afficher un flux en le renvoyant vers «rien» («/dev/null» pour Gnu/Linux, et «NUL» pour Windows).

Un petit avantage du Bash est la possibilité d’utiliser «>» (supérieur à) simple ou double «>>». Le simple permettant d’effacer le fichier de sortie avant d’écrire et le double permettant d’écrire à la suite du contenu déjà présent. Le «>» de Batch est comme un «>>» de Bash, il faut donc explicitement effacer ou vider le fichier quand on en a besoin.

La redirection de l’entrée standard est aussi possible dans les deux langages. Le Bash est un peu plus flexible avec la possibilité d’utiliser le double «<<» (inférieur à), plutôt que le simple («<») pour inclure directement dans le script le contenu souhaité, au lieu de stocker cela dans un fichier à part.

L’encodage

L’encodage pose un sérieux soucis au niveau du Batch. À quoi sert d’avoir le bon encodage ? Je répondrait que c’est, d’une part pour que les accents et autres caractères spéciaux apparaissent correctement sur sur la console, et d’une autre, pour que les diverses commandes se comprennent entre elles.

Le soucis que j’ai relevé avec le Batch, c’est que la console Windows, et beaucoup de ses commande utilise l’encodage CP-850 (ouais ça craint pas mal déjà). Ce serait passable (quoi que…) si c’était homogène entre toutes les commandes. Le problème est que la commande ftp accepte des fichiers de commandes dans un autre encodage. Je n’ai pas pris la peine de regarder lequel précisément. En tout cas vous ne pouvez pas, aisément générer un fichier de commandes ftp avec des accents et/ou des caractères spéciaux.

Le caractère d’échappement

En Bash, il y a une chose magnifique que l’on appelle le caractère d’échappement. Le plus souvent (je n’en ai jamais vu d’autre) c’est le «\» («backslash», ou anti-slash, ou contre-barre oblique, ou encore beaucoup d’autres). Le Bash, en premier lieu, l’utilise pour traiter le caractère qui suit de la même façon qu’un autre caractère standard. Par exemple :

echo \&\|

Cela donne :

&|

Si l’on ne met pas les «\», le caractère «&» (esperluette) est considérer comme un OU, c’est à dire que si, et seulement si, la commande située à gauche ne renvoie pas 0, il passe à celle de droite. S’il est à la fin d’une ligne de commande il sert normalement à passer la commande en arrière-plan. Le «|» (barre verticale) sert normalement au «pipelines» (tunnels, consistent à rediriger la sortie d’une commande vers l’entrée d’une autre).

Le caractère d’échappement permet dans un second temps d’envoyer un caractère à une commande qui doit le traiter comme un autre caractère standard aussi. Nous avons «sed» par exemple qui utilise des symboles dans ses expressions régulières :

echo “test[” | sed -e “s/\[//g”

Résultat :

test

Il peut arriver qu’une commande puisse interpréter un caractère que Bash lui-même pourrait interpréter. Il faut pour cela, que Bash envoie à la commande le caractère précédé du caractère d’échappement. Le caractère d’échappement étant interprété par Bash, il faut, en plus d’échapper le caractère, échapper l’échappement aussi. Voici un exemple :

ssh root@srv.com “echo \\\””

Resultat :

Les expansions

Les expansions, disponibles uniquement sur Bash, permettent de générer des chaînes dont seulement une partie de celles-ci change. C’est utile pour, par exemple, lister les fichiers présent dans des sous-dossiers avec des noms identiques dans plusieurs dossiers parents. Voici ce que ça donne :

du -sh /home/{john,berthe}/src

Cette commande vous renvoie l’espace utilisé par les dossiers /home/john/src et /home/berthe/src.

Les fonctions

Encore une chose que Bash a en plus, les fonctions. Bon d’accord, il n’y a pas de notion de durée de vie des variables. Bon d’accord, il manque le «return» disponible dans les autres langages (il y a un «return» mais qui n’a pas la même fonctionnalité). C’est en gros une procédure, qui peu prendre des arguments en entrée. Avec un «echo» à la place du «return» habituel, et en appelant la fonction via la substitution («$(nomfonction param1 param2)»), vous obtenez un résultat similaire à une fonction classique. Je vous mets ci-dessous un exemple. Je ne rentrerai pas dans les détails, pour ce billet en tout cas.

function carre () {
echo $(( $1 * $1 ));
};
echo “Le carré de 2 est $(carre 2).”;
echo “Le carré de 4 est $(carre 4).”;

Cela donne:

Le carré de 2 est 4.
Le carré de 4 est 16.

En Batch, il est possible de faire un simili de fonction avec les étiquettes et les «goto». Cela ressemble à ça:

ECHO Coeur du programme
CALL :FONCTION1
CALL :FONCTION2
GOTO :EOF

:FONCTION1
ECHO Coeur de la fonction 1
GOTO :EOF

:FONCTION2
ECHO Coeur de la fonction 2
GOTO :EOF

Ce n’est pas esthétique, et c’est, en plus très limité.

La date

En Batch, pour générer un horodatage («timestamp»), il faut utiliser la variable «%DATE%» qui ressemble à «12/03/2015» et la variable «%TIME%» qui ressemble à «13:46:37,50». Il faut découper des sous-chaîne, les concaténer, faire des bouts de codes pour transformer le format, etc.

En Bash, vous avez la commande «date». Ultra flexible, elle vous permet d’afficher, au choix, heures, minutes, secondes, UTC ou non, jour de la semaine, numéro, mois, années, dans des formats variés (le mois abrégé, en toute lettre, ou en chiffre, par exemple). Rendez-vous donc sur «man date».

Conclusion

Pour conclure ce billet, je dirais simplement «vive Bash !». Il n’a pas une syntaxe tout à fait «standard» de langage de programmation, mais comprenons aussi que c’est un interpréteur de commandes et qu’il a pas mal de contraintes à respecter. Je dirais aussi qu’il faut remercier la communauté du logiciel libre (Gnu en particulier) pour tous les fabuleux outils disponibles. Si vous être malheureusement sous Windows mais que vous avez la possibilité, installez Cygwin. Malgré la latence dû au diverses transformations faites en direct, vous pourrez développer vos scripts d’une manière radicalement plus facile et flexible.

Initiation à Grep par l’exemple

C’est un court billet que je vous écris aujourd’hui pour effleurer avec vous la puissance de la commande grep avec les motifs regexp.

Le contexte :

Mon besoin était simple, extraire tous les liens vers des fichiers MP3 provenant d’une page web, préalablement enregistrée.

Notes :
Je me dois de vous rappeler que le format MP3 n’est pas un format ouvert et qu’il faut, quand vous avez la possibilité, favoriser des formats ouverts, comme OGG. Je vous invite à visiter formats-ouverts.org.
Ici, l’excellent podcast de vulgarisation scientifique PodcastScience.fm ne fournit malheureusement pas encore ses émissions dans un format ouvert.

Application :

J’ai donc téléchargé la page de leur flux RSS où tous les liens vers des fichiers MP3 apparaissent, mélangés à beaucoup d’autres choses. J’ai enregistré la page sous le nom ~/podcastscience.html.
J’ai ensuite ouvert un terminal et j’ai lancé la commande :

$grep -o -e http://feed[\-_=/.?\&\;%[:alnum:]]*mp3 podcastscience.html > liste_mp3.lst

 

Explication sur la commande :

Grep est une commande permettant d’extraire du texte à partir d’un autre, en fonction d’un «motif».

L’option -o permet de n’afficher que le bout de chaîne qui correspond au motif recherché, et non la ligne entière sur laquelle il apparait.

L’option -e indique explicitement que ce qui suit est le motif.

Le dernier argument désigne le fichier à analyser.

J’ai ensuite redirigé la sortie de ma commande vers un fichier, pour le réutiliser (avec un «wget -i» par exemple), avec «> list_mp3.lst».

Explication sur le motif :

«http://feed» defini le début des chaînes recherchées. Ensuite entre «[» et «]», je place tout le jeu de caractères qu’il est possible de trouver, suivi d’un asterik («*») pour rechercher une succession multiple de ces caractères. Je termine par «mp3» car mes liens doivent se terminer par ces trois lettres. «[:alnum:]» signifie tous les caractères alphanumériques. Les caractères pouvant être interprétés par Bash ou par l’interpréteur de motif lui-même, doivent être précédé d’une contre oblique (anti-slash).

Pour résumé, ma commande grep accèpte donc les chaînes de caracèteres commençant par «http://feed», suivi d’une multitude de caractères parmi l’ensemble comprenant, «a» à «z», «A» à «Z», «0» à «9», «-», «_», «=», «/», «.», «?», «&», «;», «%», et se terminant par «mp3».

 

Voilà, j’espère que mes explications ont été assez claires et aérées. J’espère aussi que, comme moi, vous avez pu apercevoir le gros potentiel de grep.

Ma principale source pour ce billet a été la commande «man grep», et plus particulièrement le chapitre «REGULAR EXPRESSION» (abrégé REGEX ou REGEXP).
Comme d’habitude, n’hésitez surtout pas à me laisser vos remarques, critiques et suggestions.

Merci et à bientôt.