Comment tester si une commande est définie ?#
En TeX#
Le programme TeX original, écrit par Donald Knuth, ne définit pas de commandes dédiées à cette tâche. Heureusement, ε-TeX définit deux primitives supplémentaires :
\ifdefined
\ifcsname cmd name\endcsname
Les deux commandes utilisées dans l’exemple qui suit produisent le même effet :
% !TEX noedit
\ifdefined\foo
\message{\string\foo\space is defined}%
\else
\message{no command \string\foo}%
\fi
%
\ifcsname foo\endcsname
\message{\string\foo\space is defined}%
\else
\message{no command \string\foo}%
\fi
However, after using the original LaTeX \@ifundefined{foo}...
, the conditionals will detect the command as “existing” (since it has been \let
to \relax
) ; so it is important not to mix mechanisms for detecting the state of a command.
En LaTeX#
Quand on programme en LaTeX, on peut directement utiliser \@ifundefined{⟨cmd name⟩}{⟨action1⟩}{⟨action2⟩}
, qui exécute ⟨action1⟩
si la commande **n’**est pas définie, et ⟨action2⟩
dans le cas contraire (⟨cmd name⟩
est le nom de la commande tout nu, sans son antislash \
).
Si vous utilisez une version de LaTeX antérieure à 2018, il faut éviter de mélanger du code qui utilise les primitives d’ε-TeX avec du code qui utilise \@ifundefined
(voir ci-dessous pourquoi). Comme cela peut se produire d’une extension à l’autre, vous n’êtes jamais à l’abri d’une erreur…
Notez également que, même après 2018, LaTeX va toujours renvoyer “vrai” si l’on utilise \@ifundefined
avec une commande définie comme un alias de \relax
.
Un peu d’histoire#
On trouve dans d’anciennes macros écrites en TeX le procédé suivant pour tester l’existence d’une commande ⟨commande⟩
:
\ifx\⟨commande⟩\undefined⟨code à exécuter⟩
(Ceci exécute le code si la commande **n’**existe pas, bien sûr.)
Le fonctionnement de cette commande repose sur le principe que \undefined
n’est jamais défini (donc elle est égale à une autre commande non définie). Le problème est qu’il ne s’agit que d’une convention qui peut être ignorée par un autre auteur de macros : il y a donc toujours un risque que cette macro soit définie dans une extension chargée par l’utilisateur… Utiliser \@undefined
, comme on peut le voir dans certaines macros LaTeX, ne fait que déplacer le problème.
La macro \@ifundefined
, elle, est définie dans le noyau de LaTeX, ce qui permet d’éviter ce problème. Cependant, avant 2018, elle était définie de la manière suivante :
% !TEX noedit
\expandafter \ifx \csname cmd name\endcsname \relax
Elle utilisait la propriété suivante de \csname
: si la commande n’existe pas, elle est créée comme alias de \relax
. Cette approche présente deux inconvénients :
Chaque utilisation de
\@ifundefined
avec un nom de commande qui n’existe pas crée cette commande, définie comme identique à\relax
; si cette commande n’est pas redéfinie ensuite, elle est conservée inutilement en mémoire par le moteur TeX ;Si le même nom de commande est testé ensuite avec la primitive ε-TeX
\ifdefined
(par exemple dans le code d’une autre extension), le résultat sera un faux positif, car cette primitive considère aussi comme définie la commande\relax
et ses alias.
Avant que \@ifundefined
ne soit redéfinie dans le noyau LaTeX pour être basée sur la primitive ε-TeX \ifdefined
, David Kastrup a proposé la solution suivante :
% !TEX noedit
{\expandafter}\expandafter\ifx \csname cmd name\endcsname\relax ...
La commande testée est créée et définie comme \relax
à l’intérieur du groupe dans lequel est inclus le premier \expandafter
: elle n’est donc pas conservée en mémoire après l’exécution de \@ifundefined
.
Source : Is this command defined?