Quand j'ai créé la liste AppleScript francophone, j'ai dispensé aux nouveaux venus des petits cours sans prétention pour leur mettre le pied à l'étrier.
Je récidive ici, et en toute humilité, avec un exemple complexe disséqué je l'espère avec clarté.
Un shell est un environnement de travail, en tant que tel, il doit mettre à disposition de l'utilisateur des outils pour lui faciliter la tâche. Pour ce faire, les shells proposent un certain nombre de commandes intégrées appelées "builtin" (man builtin). On trouve parmi celles-ci : cd, alias, login, bg, echo, ...
L'avantage d'une commande intégrée face à une commande externe c'est, entre autre, la rapidité d'exécution. Prenons par exemple cette boucle, qui utilise la commande "echo" intégrée à Bash :
# on execute la boucle tant que i < 50000 # la boucle incrémente i, puis fait un echo de # la chaîne 'toto' dans /dev/null (un trou noir) i=0; time while [ $i -lt 50000 ]; do let i=$i+1 echo 'toto' > /dev/null done
elle donne ce résultat sur un FreeBSD 5.4 (bash) :
real 0m4.115s <-- temps total d'exécution user 0m3.258s <-- temps passé dans la sphère "utilisateur" sys 0m0.857s <-- temps passé dans la sphère "système"
La même boucle utilisant la commande /bin/echo donne ce résultat :
real 1m54.043s user 0m11.127s sys 1m54.990s
Le temps passé à faire les echos reste relativement faible (user), mais le temps de chargement de la commande alourdi énormément la boucle, si bien qu'au final, on passe de 4 secondes à 1m54 quand on décide d'utiliser une commande binaire à la place de l'équivalent "builtin".
Faites pareil sur un Mac OS X, et vous verrez qu'un des points faibles du système Apple se situe justement dans les étapes de chargement de commande (fork). La différence de temps est alors effrayante.
Il est donc intéressant d'utiliser les commandes intégrées quand elles sont disponibles. D'ailleurs, le shell le fait pour vous automatiquement dès l'instant où vous appelez la commande par son nom, et pas par son chemin :
echo toto <-- builtin /bin/echo toto <-- commande externe
Vous pouvez faire une pause ici, ça devient nettement plus ardu dans la suite.
Si on s'intéresse de plus prêt aux équivalents externes des builtins, on découvre qu'ils sont de plusieurs types. il y a des exécutables binaires et des scripts shells :
... /usr/bin/cd: Bourne shell script text executable /usr/bin/command: Bourne shell script text executable /bin/echo: Mach-O executable ppc ...
En prenant /usr/bin/cd, ou tout autre fichier de type Bourne shell script de la liste on découvre qu'ils sont identiques et contiennent : (j'ai numéroté les lignes)
1| #!/bin/sh 2| # $FreeBSD: src/usr.bin/alias/generic.sh,v 1.1 2002/07/16 22:16:03 wollman Exp $ 3| # This file is in the public domain. 4| ${0##*/} ${1+"$@"}
la ligne 1 annonce le début d'un script shell :
#! <-- le shebang. /bin/sh <-- le shell utilisé pour exécuter le script.
Les lignes 2 et 3 sont du commentaire, seule la ligne 4 est active :
${0##*/} ${1+"$@"}
Elle est très complexe pour le novice, prenons les choses dans l'ordre.
Le shell utilise un environnement qui contient des variables. Parmi ces dernières, Bash renseigne la variable $0 avec le nom/chemin du programme qui est exécuté. Ainsi, si j'ai un script shell "monscript.sh" comme ceci :
#!/bin/sh echo $0
et que je le lance comme ça :
/Users/patpro/monscript.sh
il va retourner "/Users/patpro/monscript.sh" comme résultat. car c'est le chemin de mon programme (celui qui exécute "echo $0
").
Dans le shell il est parfois important de protéger les variables, ou de les modifier à la volée. Pour ce genre d'opération, on introduit des accolades : $0
devient ${0}
.
Les manipulations de variables disponibles dans Bash sont classiques : substitutions, découpe de chaîne, ...
##<expression> correspond à une suppression de la plus grande chaîne correspondant à <expression> à partir du début de la chaîne complète. Cela veut dire que pour $0
valant "/Users/patpro/monscript.sh
", ${0##*/}
vaudra la chaîne "/Users/patpro/monscript.sh
" à laquelle on enlève */
.
*
signifie n'importe quel caractère, et /
est simplement un "/". Donc notre expression correspond à n'importe quel caractère suivi d'un /.
Si on repart de notre chaîne initiale, cela donne :
$0 -> "/Users/patpro/monscript.sh" ${0##*/} -> "monscript.sh"
Le bloc suivant est aussi complexe. On sait maintenant que ${1}
est la version protégée de $1
. Cette variable correspond au premier argument passé à une commande. Si je veux sélectionner le second argument, j'utilise $2
, et ainsi de suite. Pour sélectionner tous les arguments je peux utiliser $@
. Je modifie monscript.sh pour ajouter cette variable :
#!/bin/sh echo ${0##*/} ${1}
je l'exécute en ajoutant un argument :
/Users/patpro/monscript.sh premier_argument
Il retourne :
monscript.sh premier_argument
Le signe + figurant après le 1 n'est pas un signe d'addition, c'est un opérateur qui signifie si ma variable est définie, alors utilise l'expression qui suit, sinon, utilise une valeur nulle. Dans notre cas, l'expression qui suit est "$@
", c'est à dire l'ensemble des arguments passés à la commande.
Donc mon ${1+"$@"}
signifie que si j'ai au moins un argument défini, alors je récupère tous les arguments, si je n'ai pas d'argument défini, alors je retourne un chaîne nulle.
Récapitulons.
Le script de départ est ${0##*/} ${1+"$@"}
(sans le echo de monscript.sh). Donc quand je le lance, il lance en fait `nom_de_la_commande arguments_éventuels`.
Qu'est ce que cela implique pour notre commande externe cd
?
Quand on lance `/usr/bin/cd /tmp
`, on lance en fait un script shell qui exécute `cd /tmp
`. On voit que si on appelle la commande externe, cette dernière se rabat sur la commande interne.
C'est en fait le cas pour une quinzaine d'équivalent externes des builtins. Les exceptions notables sont /bin/echo
, /usr/bin/false
, /bin/kill
, /usr/bin/login
, /usr/bin/nice
, /usr/bin/nohup
, /usr/bin/printenv
, /bin/pwd
, /bin/test
, /usr/bin/time
, /usr/bin/true
, qui sont des binaires. Il faut consulter les sources de ces derniers pour vérifier leur comportement individuel.
Vous pouvez aller boire une bière maintenant :)
Pour aller un peu plus loin, n'hésitez pas à consulter les autres articles sur bash !