Il y a une grande différence entre un buildin et un mot-clé, dans la façon dont Bash analyse votre code. Avant de parler de cette différence, dressons une liste de tous les mots-clés et builtins :
Constructions :
$ compgen -b
. : [ alias bg bind break
builtin caller cd command compgen complete compopt
continue declare dirs disown echo enable eval
exec exit export false fc fg getopts
hash help history jobs kill let local
logout mapfile popd printf pushd pwd read
readarray readonly return set shift shopt source
suspend test times trap true type typeset
ulimit umask unalias unset wait
Mots-clés :
$ compgen -k
if then else elif fi case
esac for select while until do
done in function time { }
! [[ ]] coproc
Notez que, par exemple [
est un buildin et que [[
est un mot-clé. Je vais utiliser ces deux-là pour illustrer la différence ci-dessous, car ce sont des opérateurs bien connus : tout le monde les connaît et les utilise régulièrement (ou devrait le faire).
Un mot-clé est analysé et compris par Bash très tôt dans son analyse syntaxique. Cela permet par exemple ce qui suit :
string_with_spaces='some spaces here'
if [[ -n $string_with_spaces ]]; then
echo "The string is non-empty"
fi
Cela fonctionne bien, et Bash affichera volontiers
The string is non-empty
Notez que je n'ai pas cité $string_with_spaces
. Considérant ce qui suit :
string_with_spaces='some spaces here'
if [ -n $string_with_spaces ]; then
echo "The string is non-empty"
fi
montre que Bash n'est pas heureux :
bash: [: too many arguments
Pourquoi cela fonctionne-t-il avec des mots-clés et pas avec des modules intégrés ? parce que lorsque Bash analyse le code, il voit [[
qui est un mot-clé, et comprend très tôt qu'il est spécial. Donc il va chercher la fermeture ]]
et traitera l'intérieur d'une manière spéciale. Un builtin (ou commande) est traité comme une commande réelle qui va être appelée avec des arguments. Dans ce dernier exemple, bash comprend qu'il doit exécuter la commande [
avec des arguments (affichés un par ligne) :
-n
some
spaces
here
]
puisque l'expansion des variables, la suppression des guillemets, l'expansion des noms de chemin et le découpage des mots se produisent. La commande [
s'avère être construit dans le Shell, il l'exécute donc avec ces arguments, ce qui entraîne une erreur, d'où la plainte.
En pratique, vous verrez que cette distinction permet un comportement sophistiqué, qui ne serait pas possible avec des builtins (ou des commandes).
Mais en pratique, comment distinguer un buildin d'un mot-clé ? C'est une expérience amusante à réaliser :
$ a='['
$ $a -d . ]
$ echo $?
0
Lorsque Bash analyse la ligne $a -d . ]
il ne voit rien de spécial (c'est-à-dire pas d'alias, pas de redirections, pas de mots-clés), il se contente donc d'effectuer l'expansion des variables. Après l'expansion des variables, il voit :
[ -d . ]
exécute donc la commande (builtin) [
avec des arguments -d
, .
et ]
ce qui, bien sûr, est vrai (cela ne fait que tester si .
est un répertoire).
Maintenant, regardez :
$ a='[['
$ $a -d . ]]
bash: [[: command not found
Oh. C'est parce que lorsque Bash voit cette ligne, il ne voit rien de spécial, et donc développe toutes les variables, et finit par voir :
[[ -d . ]]
À ce moment-là, les expansions d'alias et le balayage des mots-clés ont été effectués depuis longtemps et ne le seront plus, donc Bash essaie de trouver la commande appelée [[
ne le trouve pas, et se plaint.
Dans le même ordre d'idées :
$ '[' -d . ]
$ echo $?
0
$ '[[' -d . ]]
bash: [[: command not found
et
$ \[ -d . ]
$ echo $?
0
$ \[[ -d . ]]
bash: [[: command not found
L'expansion d'Alias est aussi quelque chose d'assez spécial. Vous avez tous fait ce qui suit au moins une fois :
$ alias ll='ls -l'
$ ll
.... <list of files in long format> ....
$ \ll
bash: ll: command not found
$ 'll'
bash: ll: command not found
Le raisonnement est le même : l'expansion des alias se produit bien avant l'expansion des variables et la suppression des guillemets.
Mot-clé vs. Alias
Maintenant, que pensez-vous qu'il se passe si nous définissons un alias comme étant un mot-clé ?
$ alias mytest='[['
$ mytest -d . ]]
$ echo $?
0
Oh, ça marche ! donc les alias peuvent être utilisés pour aliaser des mots clés ! c'est bon à savoir.
Conclusion : builtins vraiment se comportent comme des commandes : ils correspondent à une action en cours d'exécution avec des arguments qui subissent une expansion directe de la variable, le découpage des mots et le globbing. En fait, c'est comme avoir une commande externe quelque part dans le fichier /bin
ou /usr/bin
qui est appelé avec les arguments donnés après l'expansion des variables, etc. Notez que lorsque je dis c'est vraiment comme avoir une commande externe Je veux seulement dire en ce qui concerne les arguments, le découpage des mots, le globbing, l'expansion des variables, etc. Un buildin peut modifier l'état interne du Shell !
Les mots-clés, en revanche, sont analysés et compris très tôt, et permettent un comportement sophistiqué du Shell : le Shell pourra interdire le découpage des mots ou l'expansion des noms de chemin, etc.
Maintenant, regardez la liste des builtins et des mots-clés et essayez de comprendre pourquoi certains doivent être des mots-clés.
!
est un mot-clé. Il semble qu'il serait possible d'imiter son comportement avec une fonction :
not() {
if "$@"; then
return 1
else
return 0
fi
}
mais cela interdirait des constructions comme :
$ ! ! true
$ echo $?
0
(dans ce cas, je veux dire not ! true
qui ne fonctionne pas) ou
$ ! { true; }
echo $?
1
Idem pour time
Il est plus puissant d'en faire un mot-clé, de sorte que les commandes composées complexes et les pipelines avec redirections puissent être chronométrés :
$ time grep '^#' ~/.bashrc | { i=0; while read -r; do printf '%4d %s\n' "$((++i))" "$REPLY"; done; } > bashrc_numbered 2>/dev/null
Si time
alors qu'une simple commande (même intégrée), elle ne verrait que les arguments grep
, ^#
et /home/gniourf/.bashrc
La sortie de la commande est ensuite envoyée dans les autres parties du pipeline. Mais avec un mot-clé, Bash peut tout gérer ! il peut time
le pipeline complet, y compris les redirections ! Si time
était un simple ordre, nous ne pourrions pas le faire :
$ time { printf 'hello '; echo world; }
Essayez-le :
$ \time { printf 'hello '; echo world; }
bash: syntax error near unexpected token `}'
Essayez de le réparer ( ?):
$ \time { printf 'hello '; echo world;
time: cannot run {: No such file or directory
Sans espoir.
Mot-clé ou alias ?
$ alias mytime=time
$ alias myls=ls
$ mytime myls
Que pensez-vous qu'il se passe ?
Vraiment, un intégré est comme une commande, sauf qu'il est construit dans le Shell, alors qu'un mot-clé est quelque chose qui permet un comportement sophistiqué ! nous pouvons dire que cela fait partie de la grammaire du Shell.