Le Z Shell
Le Bourne Again SHell (BASH pour les intimes) est sans l'ombre d'un doute le shell le plus répandu parmi les distributions GNU/Linux. Mais ce n'est pas le seul disponible et encore moins le plus puissant. Tcsh, pdksh, Perl shell, csh, etc. sont autant de shells libres disponibles. Chacun d'eux possède ses caractéristiques et ses avantages. Parmi tous, il en est un qui sort du lot : Zsh

Zsh est un shell Unix qui ressemble au Korn Shell avec lequel il est compatible. Mais un grand nombre d'améliorations ont été apportées et un certain nombre de facilités du C-Shell ont été reprises, tout comme d'autres en provenance de tcsh. Zsh est originellement écrit par Paul Falstad, qui n'en est plus le responsable officiel.

Mais que possède donc Zsh pour obtenir tant de faveurs de la part des utilisateurs ? Il s'agit en grande partie d'une souplesse de configuration et d'utilisation très intéressante. Dans cette catégorie entre par exemple la complétion non seulement des commandes, mais également des options par l'intermédiaire de fichiers de complétion. D'autre part, le langage de script de zsh comprend des facilités non négligeables.

Installation
Le Zsh est disponible sur le site portail www.zsh.org. De là, vous pourrez être dirigé vers un miroir local afin de télécharger les sources. Les possesseurs de Debian n'auront à faire qu'un simple apt-get instal zsh pour passer l'étape de l'installation. Les autres distributions incluent également zsh parmi leur package ; référez-vous directement à la procédure d'installation de logiciels pour votre distribution.

Si vous choisissez de compiler zsh sur votre système, le trio magique de commandes
./configure --prefix=/usr && make && make install devrait vous satisfaire après avoir décompressé l'archive source.

Avant de faire de zsh votre shell de login, nous vous conseillons de le tester afin de voir s'il répond bien à vos besoins (ce dont je ne doute pas un instant). Il vous suffira de le lancer en invoquant la commande :

$ zsh

Si, au terme de cet article et après avoir utilisé ce nouveau shell, vous êtes satisfait de votre nouvelle acquisition, il vous suffira d'utiliser la commande chsh (change shell) pour adopter définitivement zsh.


Organiser la configuration
De prime abord, sans fichier de configuration spécifique, zsh semble plus spartiate que Bash. Comme tout utilitaire, il nécessite une configuration propre à chaque utilisateur. Lors de son lancement, zsh cherchera à lire et à interpréter le fichier ~/.zshrc contenant les différentes directives définissant vos préférences.

La première étape consiste donc à non pas remplir à tout va ce fichier mais d'en faire un simple point d'entrée pour zsh. Considérez le contenu suivant :

for file in $HOME/.zsh/rc/*; do
        
source $file
done

Ces simples lignes nous offrent une facilité dans la gestion de notre configuration zsh. En effet, chaque fichier placé dans le répertoire ~/.zsh sera lu et interprété. Ceci nous permettra de créer plusieurs fichiers de configuration contenant chacun des commandes spécifiques.

Prompt
A présent que tout est en ordre et prêt à être utilisé dans de bonnes conditions, nous allons nous pencher sur la première chose que vous aurez sous les yeux en arrivant dans le nouveau shell : le prompt.

Ici, pas de problème, vous devez être en mesure de facilement retrouver vos marques : les variables d'environnement
PS1 à PS4 vous permettront de personnaliser tout cela. Notons au passage la présence de RPS1 permettant d'afficher du texte ou le contenu de variables sur la droite de la ligne du shell. C'est habituellement l'heure courante que l'on choisit pour cela. Bien sûr, dans le cas d'une saisie atteignant RPS1, la mention disparaîtra automatiquement.

Pour personnaliser votre prompt, il vous suffira de créer un fichier judicieusement nommé prompt dans votre
~/.zsh/rc. Celui-ci comprendra toutes les déclarations de variables dont nous avons besoin pour manipuler le prompt.

Le prompt n'est vraiment personnalisable à souhait que par l'intermédiaire de séquences d'échappement débutant par le symbole
%. Zsh offre un grand jeu de ces séquences ; vous pourrez ainsi obtenir votre prompt sur mesure. Voici un exemple :


autoload -U colors
colors

host_color="green"
path_color="blue"
date_color="yellow"

date_format="%H:%M"

date="%$fg[$date_color]%%D$date_format"
host="%$fg[$host_color]%[%n]"
cpath="%B%$fg[$path_color]%%/%b"
end="%$reset_color%"

PS1="$host$cpath$end %# "

RPS1="$date$end"

Remarquez que nous avons séparé les différentes étapes de la composition du contenu de PS1 et RPS1 :

- Le premier paragraphe nous permet d'utiliser les couleurs. Afin de vous éviter de devoir composer vous-même toutes les séquences de couleur ANSI, zsh inclut de base une fonction shell nommée colors. Nous reviendrons plus tard sur la notion de fonction utilisé par zsh.

- Nous déterminons ensuite les couleurs que nous souhaitons utiliser pour chaque élément des variables utilisées pour le prompt. Nous aurions parfaitement pu nous passer de cette étape, mais cela présente un avantage certain dans le cas d'une modification ultérieure. Nous avons ici déterminé une couleur pour chaque élément : le nom d'hôte, le chemin courant et la date.

- Nous précisons le format de notre date. Ici, nous souhaitons un format heures:minutes

- Enfin, nous pouvons composer les variables correspondant à chaque élément. La date est composée en jaune pour l'avant-plan (
%$fg[$date_color]%) et utilise le format spécifié ($date_format) dans le mode international (%D). Il en va de même pour les autres éléments. Notez la déclaration d'une variable end permettant de revenir aux couleurs de départ. Ceci nous évitera de nous retrouver avec un écran coloré de manière relativement ignoble.

- Dernière étape, nous composons les variables
PS1 et RPS1 en groupant les différentes variables que nous venons de composer. PS1 sera donc constitué du nom d'hôte suivi du chemin courant. RPS1 sera, tout simplement, la date au format et avec la couleur définie.

Nous avons fait usage ici d'un grand nombre de séquences mises à disposition par zsh (appelées percent escape). Vous trouverez une liste complète dans la documentation officielle du shell.


Historique des commandes
Tout comme la plupart des shells Unix, zsh permet de conserver un historique des commandes utilisées précédemment. Ce genre de fonctionnalités accélère grandement l'emploi du shell, en particulier pour les commandes répétitives ou longues à saisir.

Par défaut, l'historique des commandes ne fonctionnera que pour la session actuelle du shell. Ceci signifie que si vous quittez le shell et que vous le relancez, vous perdrez l'historique des commandes utilisées dans la précédente session.

Pour assurer la conservation de l'historique, vous devez spécifier un fichier et une taille par l'intermédiaire de variables d'environnement. Nous allons donc créer un nouveau fichier dans notre
~/.zsh/rc afin d'y placer toutes les variables d'environnement dont nous avons besoin, et en premier lieu celles concernant l'historique. Notre nouveau fichier (appelé envir par exemple) contiendra ceci :

HISTFILE=~/.history
SAVEHIST=1000
export HISTFILE SAVEHIST

Les trois variables en action sont :
-
HISTFILE qui détermine l'emplacement et le nom du fichier historique ;
-
SAVEHIST qui contient le nombre de lignes maximum figurant dans le fichier spécifié au-dessus.

Dès lors, toute nouvelle session du shell débutera par une lecture sur fichier
$HISTFILE puis passera en mode interactif. Vous retrouverez immédiatement votre historique de commandes.

Il reste une variable qu'il est nécessaire de spécifier :
HISTSIZE. Celle-ci permet de définir le nombre de lignes maximum à conserver dans l'historique pour la session courante. La modification est nécessaire car la valeur par défaut de 30 était définie dans l'intention d'économiser de l'espace mémoire. Ceci, avec les machines courantes de nos jours, n'est plus nécessaire. Nous pouvons donc spécifier la taille maximale correspondante à la taille de l'historique (HISTSIZE ne doit pas dépasser SAVEHIST). La version finale de notre fichier des variables d'environnement sera donc :

HISTFILE=~/.history
HISTSIZE=1000
SAVEHIST=1000

LANGUAGE=fr_FR
LC_ALL=fr_FR
LC_MESSAGE=fr_FR

export LANGUAGE LC_ALL LC_MESSAGE HISTFILE HISTSIZE SAVEHIST

Remarquez que nous en avons profité pour définir les variables d'environnement concernant les locales.

Certaines options peuvent être utilisées pour optimiser le fonctionnement de l'historique :
-
INC_APPEND_HISTORY vous permettra de partager l'historique entre les différentes sessions de zsh. Ainsi, par exemple, en lançant un shell depuis le shell courant, vous hériterez de tout l'historique précédent. Cette option est à préférer à APPEND_HISTORY qui groupe simplement les historiques de chaque shell avec un mécanisme de non redondance qui peut ralentir le système si, comme moi, vous possédez continuellement une quinzaine de shells ouverts simultanément.
-
SHARE_HISTORY va encore plus loin dans le partage de l'historique. Cette option permet à l'ensemble des shells en cours d'exécution sur l'hôte de partager un historique commun de manière dynamique. Contrairement à l'option précédente qui ne fait que répercuter l'historique dans un sens, SHARE_HISTORY établit un véritable partage entre les différentes sessions. Bien sûr, pour ne pas semer la confusion, les commandes exécutées dans d'autres sessions ne seront visibles dans l'historique courant qu'après avoir fait usage de la commande history affichant le contenu de l'historique (c'est donc une très bonne idée de créer un alias h, par exemple, de la commande history).
-
HIST_IGNORE_DUPS est très pratique. Cette option permet de ne pas enregistrer la commande dans l'historique si celle-ci est identique à la précédente. Ceci vous permettra de vous abstenir de rester appuyé sur la flèche du haut jusqu'à trouver la bonne commande.
-
HIST_IGNORE_ALL_DUPS permet de pousser encore plus loin l'optimisation. La précédente option permet d'éliminer les répétitions de commandes dans l'historique, mais si vous utilisez SHARE_HISTORY (par exemple), il peut arriver que des commandes identiques (et répétées) ne soient pas placées de manière contiguë dans l'historique. HIST_IGNORE_DUPS ne vous sera alors d'aucun secours. La nouvelle option vous permet de supprimer automatiquement une entrée identique déjà présente dans l'historique. Vous êtes ainsi en possession d'un historique optimisé et propre.

Ces différentes options sont définies par l'intermédiaire de la commande setopt dans l'un de vos fichiers de configuration (
~/.zsh/rc/hist par exemple).

Dernier point concernant l'historique, la commande history vous permet, grâce aux options
-f et -D d'afficher respectivement la date/heure de l'entrée et le temps d'exécution écoulé. Ceci peut s'avérer très utile pour des manipulations éprouvantes pour le système.


Complétions
Vous connaissez sans doute le pouvoir et l'aisance d'utilisation de la touche de tabulation dans un shell. Celle-ci permet l'auto-complétion des commandes. Ainsi, en tapant le début d'une commande comme "mut", le shell, après pression sur la touche magique, complétera votre chaîne de caractères pour donner mutt qui correspond à la commande du même nom disponible sur le système.
Cette méthode existe également pour les noms de fichiers : en tapant cat suivi d'une ou deux pressions sur
<TAB>, cela vous affichera les fichiers susceptibles d'être "dumpés" dans le répertoire courant.

La complétion des commandes et des fichiers est un domaine où zsh excelle. Il peut non seulement être utilisé comme nous venons de l'expliquer, mais est capable de proposer un choix bien plus précis en fonction des règles que vous aurez déterminées.

Prenons l'exemple simple de la commande cd destinée à changer de répertoire. En temps normal, la complétion de cd vous propose tous les fichiers courants. Or, cette commande ne s'applique qu'aux répertoires et nous n'avons que faire des fichiers classiques.

Les nouvelles versions de zsh utilisent un système de complétion basé sur des fonctions. L'ancien système reste valide et largement utilisé. Les fonctions zsh se comportent de la même manière qu'avec un langage de programmation ; celles-ci sont appelées et retournent des valeurs directement utilisables. Dans le cas de la complétion, les valeurs retournées correspondent aux arguments de la commande tapée, qu'il s'agisse de nom de fichier, d'option ou autres. Zsh comprend, de base, un grand nombre de fonctions prédéfinies. Celles-ci sont stockées dans un répertoire pointé par la variable $fpath (function path). Vous trouverez là quelques 350 fichiers comprenant des fonctions qui, pour la majeure partie, concernent la complétion.

Nous devons, avant toute chose, activer l'utilisation de ces fonctions. Pour cela, créez votre fichier de complétion dans votre
~/.zsh/rc (nous l'appellerons, par exemple, completion) :

autoload -U compinit
compinit

Normalement, cet ajout ne devrait déclencher d'affichage que lors du lancement d'une nouvelle session zsh. Dans le cas contraire, vérifiez que le chemin dans $fpath est correct et que les fichiers s'y trouvent effectivement.

Si tout se passe correctement, notre problème de la commande cd doit déjà être résolu. Nous avons, en effet, activé la complétion avancée de zsh et cd est pris en charge automatiquement.
Mais nous pouvons personnaliser le style de complétion que nous désirons utiliser. Nous employons la commande interne zstyle pour cela. Celle-ci prend en argument un contexte qui prend la forme d'une chaîne composée ainsi :

:completion:<func>:<completer>:<command>:<argument>:<tag>

-
completion est l'élément fixe et commun à tous les contextes ;
-
<func> est le nom de la fonction en cause ;
-
<completer> est le type de completer à utiliser. C'est là une notion poussée du système de complétion de zsh. Dans la majorité des cas, le completer sera complete, qui correspond à la completion simple classique.
-
<command> est le nom d'une commande dans le contexte ;
-
<argument> représente les arguments de la commande ;
-
<tag> décrit le type de complétion.

Nous allons voir immédiatement un exemple d'utilisation du contexte avec zstyle. Ajoutez ceci dans votre fichier comp :

zstyle ':completion:*:descriptions' format '%B%d%b'

Cela nous permettra d'afficher la description du format (
%d) en gras (%B...%b) pour toutes les fonctions (*). Après avoir ajouté cette ligne, la commande cd suivie de la touche <TAB> vous affichera non seulement la liste des répertoires, mais également la mention "directory" correspondant au type d'informations retournées par la fonction.

De la même manière, nous pouvons ajouter :

zstyle ':completion:*:warnings' format 'No matches for: %d'

Cela concerne les messages d'alerte relatifs à la complétion. En temps normal, aucun message n'est retourné si aucune correspondance n'a pu être trouvée. Nous modifions cela en précisant le format précédé par la mention "No matches for:". Au final, la commande cd suivie d'un nom de répertoire erroné que l'on tente de compléter affichera :

No matches for: `directory'

Avec la commande cat, kill ou encore xv, nous obtiendrons alors respectivement :

No matches for: `file'
No matches for: `process ID' or `job'
ou
No matches for: `picture file', `file', or `corrections'

Pour finir cette section, voici un extrait du fichier de configuration par défaut en ce qui concerne la complétion :

# liste des completers à utiliser
zstyle ':completion:*::::' completer _expand _complete _ignored _approximate

# autorise un caractère sur trois à être une erreur de typo
zstyle -e ':completion:*:approximate:*' max-errors par
         'reply=( $(( ($#PREFIX+$#SUFFIX)/3 )) numeric )'

# insère toutes les possibilités pour le completer expand
zstyle ':completion:*:expand:*' tag-order all-expansions

# formatage et décoration
zstyle ':completion:*' verbose yes
zstyle ':completion:*:descriptions' format '%B%d%b'
zstyle ':completion:*:messages' format '%d'
zstyle ':completion:*:warnings' format 'No matches for: %d'
zstyle ':completion:*:corrections' format '%B%d (errors: %e)%b'
zstyle ':completion:*' group-name ''

# essaie les minuscules après les majuscules
zstyle ':completion:*' matcher-list 'm:a-z=A-Z'

# les extensions de fichier à ne pas proposer (sauf pour rm)
zstyle ':completion:*:*:(^rm):*:*files' ignored-patterns par
         '*?.o' '*?.c~' '*?.old' '*?.pro'


Ajouter une fonction de complétion
Avant toute chose, ne placez JAMAIS vos propres fichiers de fonction de complétion dans le répertoire d'installation de zsh (pointé originellement par $fpath). Créez votre propre répertoire, par exemple ~/zfunc, et placez-y vos fichiers. Vous pourrez ajouter ce chemin de recherche des fonctions zsh en ajoutant la ligne suivante à votre configuration :

fpath=(~/zfunc $fpath)

Remarquez que le répertoire en question est ajouté en début de la variable d'environnement. Ceci vous permettra de remplacer des fonctions existantes par les vôtres sans avoir à toucher au répertoire d'installation. En effet, les fichiers fonction ne sont pas chargés directement au démarrage du shell mais par le mécanisme de chargement automatique lorsqu'une fonction est demandée.

Ceci fait, nous pouvons écrire notre première fonction. Celle-ci débutera par le caractère _ prévu à cet effet. Tous les noms de fonctions zsh commencent par ce symbole et ce, qu'il s'agisse de fonctions définies dans un fichier ou du fichier de fonction lui-même. Notre petite mise en pratique portera sur l'utilitaire GnuPG qui ne semble pas encore posséder de fonction propre.

Notre fichier s'appellera tout naturellement _gpg et sera placé dans ~/zfunc. En premier lieu, nous devons spécifier à quelle commande se rapporte cette fonction :

#compdef gpg

Dans un premier temps, nous nous limiterons à proposer quelques arguments couramment utilisés avec GnuPG :

_arguments -s par
         '--list-keys' par
         '--recv-keys' par
         '--edit-key' par
        
'--list-sig' par
        
'--sign' par
         '--verify' par
        
'--version'

Rien de bien méchant ici, nous utilisons la fonction _arguments en lui passant une liste d'options à proposer. Dès lors, la complétion devrait déjà fonctionner et vous proposer les différents choix définis dans la fonction. Malheureusement, nous ne pouvons nous satisfaire de cela. En effet, l'option verify, par exemple, attend en argument un fichier de signature et un nom de fichier correspondant à la signature.

Nous devons donc prendre en compte ces informations pour la complétion en remplaçant la ligne
'--verifiy' par :

'--verify:signature:_files -g *.(asc|gpg|pgp|sign):fichier:_files'

Ici, nous proposons non seulement l'option --verify, mais celle-ci est complétée avec deux autres arguments. Ceux-ci sont retournés par la fonction _files permettant la proposition de noms de fichiers. La première occurrence de _files précise une sélection sur l'extension qui correspond aux suffixes habituellement utilisés pour les fichiers signatures. La seconde occurrence de _files ne précise aucun filtre. Tous les fichiers sont donc proposés.

Le test est aisé :

# ls
patch-2.4.5.gz
patch-2.4.5.gz.sign

# gpg --<TAB>
option
--edit-key
--list-keys
--list-sig
--recv-keys
--sign
--verify
--version

# gpg --verify <TAB><TAB>
# gpg --verify patch-2.4.5.gz.sign patch-2.4.5.gz<CR>
gpg: Signature made sam 26 mai 2001 03:27:13 CEST using DSA key ID 517D0F0E
gpg: Good signature from "Linux Kernel Archives Verification Key <ftpadmin@kernel.org>"
[...]

Et voilà ! Le tour est joué. Vous pouvez compléter cette fonction avec le reste des options de GnuPG. Pour des manipulations avancées, comme la proposition des noms d'utilisation de chaque clef, créez d'autres fonctions qui seront appelées depuis _gpg. Soyez cependant prudent, car écrire une fonction avancée supportant comme dans ce cas l'ensemble des options de GnuPG peut être très long et éprouvant.


L'ancien système de complétion
Si l'utilisation du nouveau système s'avère être très facile, la modification ou l'ajout de fonctions de complétion complexes nécessite des bases en termes de programmation. Heureusement, pour les utilisateurs non programmeurs ou n'ayant pas le temps de développer, il est toujours possible d'utiliser l'ancien système.

Notre fichier de configuration de complétion débutera alors avec ceci :

zmodload zsh/complist
ZLS_COLORS=$LS_COLORS

Nous commençons par charger le module zsh permettant l'affichage d'une liste de propositions en couleur puis nous spécifions que les couleurs à utiliser sont celles de la commande ls. Nous pouvons, à présent, nous occuper du cas cd cité plus haut.

Voici la ligne qu'il nous faut :

compctl -g '*(-/) .*(-/)' cd

Nous demandons une complétion de la commande cd (en fin de ligne) portant exclusivement sur les répertoires (*(-/)) et les répertoires cachés débutant par . (.*(-/)'). Cela paraît bien plus simple que le nouveau système de complétion, mais devient rapidement un problème avec des commandes complexes. Il faut alors se pencher sur le développement d'un nouvelle fonction de complétion.

Personnellement, je trouve que le nouveau système de complétion ralentit le démarrage de zsh (configuration à base de P233) et préfère utiliser l'ancien système. De plus, la modification des fonctions est quelque peu éprouvante en fonction de la complexité du fichier. Mais tout ceci est une histoire de goût et de configuration...

Finissons avec les remerciements à IC et asyd sans qui j'utiliserais toujours bash :)

Liens
Zsh homepage
http://www.zsh.org

Le site zsh français
http://www.zshfr.org

Les contributions zsh (config, script, complétion)
http://sunsite.dk/zsh/Contrib/