Un terminal comme Konsole ou Gnome Terminal est une fenêtre redimensionnable. Si l’on souhaite afficher une ligne de séparation sur toute la largeur de celui-ci, il est nécessaire de connaître le nombre de colonnes (ou de caractères) qu’il affiche, forcément variable. Une manière de connaître les dimensions du terminal consiste à lancer la commande stty : celle-ci affiche alors un nombre important d’informations, dont le nombre de colonnes du terminal en cours. Une autre manière de récupérer le nombre de colonnes, consiste plus simplement à lancer la commande tput avec pour argument « cols », dont voici un exemple :
[benoit:~]$ tput cols
237
En encapsulant, en Bash, la commande tput dans $(…), lors de l’exécution cette pseudo-variable sera automatiquement remplacée par sa valeur, 237 dans notre exemple. Voyons maintenant comment nous en servir avec printf pour écrire 237 caractères.
Sans entrer dans les détails de la commande printf, le fait d’écrire printf "%237s" " " permet d’afficher 237 espaces. Puisque notre terminal ne fera pas toujours 237 caractères de large, remplaçons cette valeur par $(tput cols) comme expliqué au-dessus.
Puisque nous souhaitons afficher un élément de séparation dans notre terminal, la série d’espaces devra être remplacée par une série d’autre chose. L’utilisation du « _ » (underscore, souligné, ou «tiret du 8») permettra de simuler une ligne tirée de bout en bout, mais le caractère « * » (étoile ou astérisque), moyennant un « petit ajustement » (ou plutôt un échappement), aurait tout aussi bien fait l’affaire. La commande sed va nous servir à remplacer chacun des espaces par un underscore, via un pipe (le fameux « | ») en sortie du printf. La fonction « printline » ci-dessous prend en compte l’ensemble des points abordés, à savoir l’utilisation de tput, printf et sed.
function printline () { printf "%$(tput cols)s\n" "" | sed s/' '/"_"/g }
Cet exemple est déjà un bon début. Maintenant, nous souhaiterions pouvoir afficher une ligne avec n’importe quel caractère, sans avoir à modifier à chaque fois notre script. Pour ce faire, nous allons donc prévoir une variable, laquelle reposera sur le premier argument passé au script. La variable $c à la place du underscore permet de remplacer chaque espace par le caractère (ou les caractères, mais dans ce cas il y aura autant de lignes que de caractères !) passé en argument. Une dernière petite astuce va nous permettre d’utiliser un underscore par défaut. L’écriture ${c:=_} s’explique ainsi : si ma variable $c ne retourne aucun caractère, alors utiliser « _ ». L’exemple ci-dessous reprend la fonction précédemment écrite avec ces quelques améliorations.
function printline () { c=$1 printf "%$(tput cols)s" "" | sed s/' '/"${c:=_}"/g | cut -c1-$(tput cols) }
Cette fonction permet d’obtenir ces différents résultats :
[benoit:~]$ printline
________________________________________________________________________[benoit:~]$ printline *
************************************************************************[benoit:~]$ printline Abc
AbcAbcAbcAbcAbcAbcAbcAbcAbcAbcAbcAbcAbcAbcAbcAbcAbcAbcAbcAbcAbcAbcAbcAbcAbcAb
J’ai étudié avec grand intérêt votre fonction printline (je débute en bash) et je butte sur un détail :
comment faire accepter à cette fonction des caractères comme * ( ) ?
J’ai appris, je ne sais où, que $COLUMNS fournissait aussi la largeur de la fenêtre. J’ai donc remplacé tput cols par $COLUMNS mais fait totalement étrange, cela fonctionne quand on frappe la commande en ligne mais absolument pas quand elle est intégrée dans la fonction !!
Autre remarque pour le sport : il serait peut-être intéressant de diviser la longueur de la ligne par la longueur du paramètre afin qu’une seule ligne soit affichée.
Cordialement
Yves
Bonjour Yves,
Merci pour vos remarques et vos questions.
Pour que la fonction accepte comme argument les caractères spéciaux, c’est très simple : il suffit de mettre le caractère entre apostrophes « ‘ » ou entre guillemets « » », ou bien de l’échapper avec un anti-slash « ». Ainsi, ces trois écritures fonctionnent et sont équivalentes :
printline '('
printline "("
printline (
La variable $COLUMNS n’existe (ou n’existait) pas sur Solaris, Unix sur lequel j’ai développé cette fonction à l’origine. Ou plus sûrement parce que je me suis heurté au même problème que vous. C’est pourquoi je suis passé par la commande tput. La raison pour laquelle vous ne pouvez pas utiliser cette variable dans la fonction, c’est parce qu’il s’agit d’une variable shell, c’est-à-dire qu’elle est gérée par le processus shell auquel est attaché votre terminal. Mais lorsque vous exécutez un script ou une fonction, il s’agit d’un processus indépendant, lequel n’a pas connaissance de votre terminal, et encore moins de ses dimensions. Utiliser tput est donc la seule solution manifestement…
Merci pour votre suggestion ! Je n’y avais pas pensé. Les possibilités sont multiples, et une solution qui me semble plus efficace encore, consiste à ne conserver que les N premiers caractères de la chaîne produite. Pour cela, il suffit d’utiliser la commande cut dans un « pipe » en faisant appel encore une fois à tput. J’en ai profité pour mettre à jour le second script dans mon article, n’hésitez pas à me dire ce que vous en pensez.
J’espère à bientôt,
Benoît.
Bonjour,
…et merci pour cette réponse hyper-didactique ! J’adore. Cette nouvelle version m’a permis d’aller étudier cut. Toujours d’après mes errances sur Internet, ne faudrait-il pas mettre
local c=$1 pour ne pas casser une éventuelle variable c de l’utilisateur ?
Bravo encore
Yves
Bonjour Yves,
En effet, c’est une éventualité à laquelle je n’avais jamais songé. D’ailleurs, je ne connaissais pas la commande « local », merci de me l’avoir fait découvrir.
J’ai expérimenté un petit peu pour voir la portée des variables, et voici mes conclusions : lorsque vous déclarez une variable dans un script shell et que vous exécutez celui-ci, cette variable n’impacte pas votre environnement (en fait celui de votre terminal). Cependant, si dans votre script shell vous avez écrit une fonction qui contient une variable, celle-ci impacte bien l’environnement d’exécution de votre script ; mais toujours pas votre environnement.
En revanche, si comme moi vous créez des fonctions que vous avez l’habitude de déclarer / charger à l’ouverture d’un terminal (classiquement via un « source » dans le fichier .bashrc), les variables déclarées dans ces fonctions risquent d’impacter votre environnement. J’ai pu m’en apercevoir en écrivant ces quelques lignes à la va-vite dans mon terminal :
[benoit:~]$ abc=123
[benoit:~]$ function toto () { echo $abc ; local abc='456' ; echo $abc ; }
[benoit:~]$ toto
123
456
[benoit:~]$ echo $abc
123
La fonction « toto » écrit d’abord le contenu de la variable $abc, qui a été déclarée auparavant. Puis $abc est remplacée par une variable locale qui contient une autre valeur, écrite en sortie à son tour. Après exécution de la fonction, $abc a toujours sa valeur initiale, ce qui n’aurait pas été le cas sans la commande « local ».
Vous connaissez certainement la commande « unset », qui permet de supprimer une variable de l’environnement ?
[benoit:~]$ hello=world
[benoit:~]$ echo $hello
world
[benoit:~]$ unset hello
[benoit:~]$ echo $hello
[benoit:~]$
Avec la commande « unset », il est aussi possible de supprimer une fonction de l’environnement en précisant en argument « -f » :
[benoit:~]$ function hello () { echo world ; }
[benoit:~]$ hello
world
[benoit:~]$ unset -f hello
[benoit:~]$ hello
-bash: hello: command not found
[benoit:~]$
Très cordialement,
Benoît.