Il est des fois où l’on perd son temps à passer d’un répertoire à l’autre depuis son terminal. Que ce soit pour d’interminables couples compilation/débogage ou pour une toute autre raison, on enchaîne les foutus « cd ceci » et les « cd celà » , sans que l’on puisse vraiment se satisfaire de dcd.
Voici deux petites fonctions qui ne vous laisseront pas de marbre. Elles sont à ajouter au démarrage du terminal dans le fichier .bashrc, toujours via la commande source :
# Variable globale pour l'utilisation des fonctions later et before
BEFORE=`pwd`
# Permet de sauvegarder le chemin actuel
# @param -q (quiet) pour que la commande n'affiche rien dans le terminal
function later ()
{
BEFORE=`pwd`
test "$1" != "-q" && echo "later <- `printcolor $GREEN $BEFORE`"
}
# Permet de sauvegarder le chemin actuel et de retourner au chemin precedemment sauvegarde
# @param -q (quiet) pour que la commande n'affiche rien dans le terminal
function before ()
{
BEFORE2=`pwd`
test "$1" != "-q" && echo "later <- `printcolor $GREEN $BEFORE2`"
test "$1" != "-q" && echo " now -> `printcolor $GREEN $BEFORE`"
cd $BEFORE
BEFORE=$BEFORE2
}
alias lt=later
alias bf=before
L’utilisation de ces fonctions s’avère alors extrêmement simple :
$ cd toto/tata/titi/
$ lt
later <- /home/moua/toto/tata/titi
$ cd ../../tutu/
$ bf
later <- /home/moua/toto/tutu now -> /home/moua/toto/tata/titi
$ bf
later <- /home/moua/toto/tata/titi now -> /home/moua/toto/tutu
Bien-sûr, pour un aspect plus sinistre, il est possible de virer les printcolor… 😯
Dans le précédent article, nous avons vu comment mettre en couleur du texte dans le terminal avec la commande printf.
Il serait parfois intéressant de pouvoir distinguer, dans le terminal, des informations plus ou moins « tabulées ». L’objectif de cet article est de présenter une solution de mise en couleurs d’un texte, c’est-à-dire un ensemble de mots séparés par des espaces. Nous allons rapidement constater une limitation liée à ces fameux espaces, ce que nous tenterons de résoudre dans un deuxième temps.
Concentrons-nous d’abord sur la manière de changer de couleur chaque mot d’un texte. Nous avons tout de même une piste : la fonction printcolor réalisée précédemment, à laquelle nous pouvons passer comme paramètre une valeur de couleur comprise entre 0 et 7. Si nous partons du principe que le fond de notre terminal est noir, nous n’utiliserons pas la valeur 0 et commencerons à 1 par conséquent.
Reste à trouver une façon simple de changer de valeur pour chaque nouveau mot de notre texte. Nous pouvons par exemple nous servir d’une boucle for à laquelle nous allons passer le texte en paramètre. Ainsi, pour chaque mot du texte, nous pouvons exécuter notre fonction printcolor. Et dans le même temps, il suffit d’incrémenter une variable initialisée à 1. Si nous détectons que notre variable $couleur vaut 8, alors nous lui réattribuons la valeur 1. La fonction echorainbow ci-dessous répond à cette première analyse.
function echorainbow ()
{
couleur=1
for word in $chaine
do
printcolor "$couleur" "$word"
couleur=$((couleur+1))
test $couleur -eq 8 && couleur=1
done
}
Après chargement en session de notre script (via la commande source), ce seul exemple devrait déjà faire de votre terminal une vraie discothèque pour geek :
[benoit:~]$ echorainbow "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam."
Mais comme un texte n’est pas non plus ce qui nous intéresse dans un terminal la plupart du temps, nous allons plutôt voir un exemple un peu plus complexe avec la commande ls. Pour un affichage ligne par ligne, il est dans ce cas nécessaire d’utiliser un petit while derrière un pipe… Si vous ne me croyez pas, essayez d’exécuter « echorainbow `ls` » ! Voici le résultat — nettement perfectible ! — de cette commande :
Première version de la fonction echorainbow
Nous nous apercevons que les espaces n’ont pas été conservés, ce qui est logique, puisque les espaces sont utilisés comme séparateurs lors de l’utilisation du for. Ajouter un espace derrière chaque mot n’est pas forcément une solution valable, puisque pour des données tabulées comme c’est le cas avec la commande ls, nous serions quand-même susceptibles de perdre l’alignement vertical permis par l’affichage de plusieurs espaces consécutifs !
Nous nous rendons compte qu’il serait beaucoup mieux de pouvoir garder les espaces. Pour ce faire, il devient donc nécessaire de remplacer chaque espace, non pas par un seul caractère, mais par une chaine de caractères, qui ne risque pas d’être rencontrée parmi tous les mots. A l’aide d’un pipe et de la commande sed, nous allons donc changer tout caractère espace, par exemple en "5PAC3". Ne voyez pas là un clin d’oeil au rappeur 2Pac, mon inspiration est surtout venue du 1337 5p34k… Voici la commande sed correspondante :
sed s/’ ‘/’5PAC3’/g
Cela ne suffit pas. Puisque les espaces nous servent de séparateurs de mots, si nous les supprimons, cela revient à ne plus avoir qu’un seul et unique « mot », mais très long. Comme quoi, parfois, plus c’est long, moins c’est bon… Euh… bref… Il nous faut donc séparer nos mots comme précédemment, par exemple en ajoutant un espace à la suite de chaque série d’espaces à conserver (et donc identifiés par notre super chaine « 5PAC »). Là encore, la commande sed va nous être utile. Mais cette fois-ci, c’est en sortie de la précédente commande sed que nous allons mettre un pipe, afin appliquer cette nouvelle transformation. Cette deuxième commande sed s’écrit ainsi (faites bien attention à l’espace derrière le « 1 » !) :
sed s/"((5PAC3)+)"/"1 "/g
Une fois les deux commandes sed mises bout à bout, voilà ce que ça donne :
sed s/’ ‘/’5PAC3’/g | sed s/"((5PAC3)+)"/"1 "/g
Il ne reste plus qu’à appliquer nos deux sed sur la chaine en entrée, puis, pour chaque mot, rechanger les "5PAC3" en espaces (ce qui peut être fait en sortie de la fonction printcolor, dans la boucle for). Revoyons notre fonction echorainbow avec ces nouveaux éléments pris en compte :
function echorainbow ()
{
esp="5PAC3"
chaine=`echo "$*" | sed s/' '/$esp/g | sed s/"\(\($esp\)\+\)"/"\1 "/g`
j=1
for word in $chaine
do
printcolor "$j" "$word" | sed s/$esp/' '/g
j=$((j+1))
test $j -eq 8 && j=1
done
}
Cette fois-ci, l’exécution de l’exemple avec le ls et le while est bien plus convaincante !
Nouvelle version de la fonction echorainbow
Bien-sûr, l’exemple avec ls n’est pas forcément le plus intéressant… Par contre, l’utilisation de cette fonction peut trouver son sens dans le cas de données « chainées », c’est-à-dire de natures diverses et séparées par un séparateur comme « # » ou « ; ». Prenons l’exemple ci-dessous :
1;2;3;hello world ; TOTO;0;TITI;TATA;123;321;Lo;rem; ip;su;m
La lecture de 10, 20 ou 30 lignes comme celle-ci peut devenir rapidement fastidieuse, surtout lorsqu’on recherche une information précise en plein milieu. Dans ce cas, notre fonction echorainbow va nous permettre de bien distinguer chaque partie… Mais à une condition : il va falloir remplacer les « ; » par des espaces… Mais à une condition (hé oui !) : il va falloir remplacer les bons espaces par un autre caractère… Puis re-remplacer tout ça par les caractères de départ, afin de profiter du même affichage, mais en couleur !
Pour ce faire, nous utiliserons (pour une seule ligne, sinon, ne pas oublier de boucler dans un while) quatre commandes sed, les deux premières avant exécution de la fonction echorainbow, les deux autres après. Ce qui donnera dans notre exemple :
[benoit:~]$ chaine=`echo ‘1;2;3;hello world ; TOTO;0;TITI;TATA;123;321;Lo;rem; ip;su;m’ | sed s/’ ‘/#/g | sed s/’;’/’ ‘/g`
[benoit:~]$ echorainbow "$chaine" | sed s/’ ‘/’;’/g | sed s/#/’ ‘/g
Surtout ne dites rien ! Admirez juste votre terminal…
Pour afficher un titre dans le terminal, on sera tenté de faire un simple « echo ‘ avec plein d’espaces devant' ». Cette solution étant quand-même très limitée (et moche, aussi !), un printf va nous permettre de faire mieux… Reste à régler le problème de la largeur du terminal, qui est déterminante dans le centrage de notre titre.
Dans le précédent article, nous avons vu l’utilisation de la commande tput. Celle-ci va nous être utile encore une fois, mais avec une subtile différence : au lieu d’utiliser directement la valeur qu’elle retourne, celle-ci sera directement employée dans un calcul.
Lorsqu’on veut centrer un texte sur une ligne, on a besoin de connaître la longueur du texte ainsi que la longueur totale de la ligne. Ici, la longueur de la ligne correspond très logiquement à la largeur de notre terminal, que la commande tput nous permet de trouver. Pour le texte, nous passerons par une variable, puisqu’il est très simple d’en connaître sa longueur avec la notation ${#…}.
Qui dit « centre », dit « distance divisée par deux ». Il est donc nécessaire de connaître le milieu du terminal, mais aussi le milieu de notre texte. Un schéma (merci GeoGebra !) peut aider à comprendre le calcul géométrique qui va suivre. Rassurez-vous, ça ne casse pas non plus trois pattes à un canard…
Centrer un texte
Considérons que le segment AB correspond à notre terminal, et le segment DE au texte que nous souhaitons centrer. Le point C correspond à la fois au milieu de AB et au milieu de DE. Nous constatons que pour centrer convenablement le texte avec la commande printf, il nous faut calculer la longueur AE, c’est-à-dire la moitié du terminal à laquelle on ajoute la moitié du texte, soit :
AE = AC + CE
AE = AB / 2 + DE / 2
C’est ce calcul que nous retrouvons dans la fonction « printcenter » ci-dessous. Attention toutefois à bien comprendre que les divisions réalisées produisent des nombres entiers ! Hé oui, 5 / 2 = 2…
Cette première fonction est déjà satisfaisante. Toutefois, on peut encore l’améliorer… Pour centrer notre texte, la commande printf complète à gauche avec des espaces, puis s’arrête juste après le texte : finalement, notre texte est justifié à droite d’une longueur AE inférieure à celle du terminal, et derrière, hé bien il n’y a absolument rien… Nous allons compléter ce vide par d’autres espaces afin d’atteindre le bout de la ligne.
Si nous reprenons notre schéma, nous constatons que ces espaces vont correspondre à la distance EB. Celle-ci se traduit par ce calcul :
EB = AB – AE
Or nous connaissons déjà ces longueurs ! Dans notre précédente fonction, il nous suffit alors de créer une seconde variable « cols » en utilisant la première. Il ne reste plus qu’à écrire une chaine d’espaces de la longueur trouvée, juste après le texte justifié à droite.
Cette nouvelle version permet maintenant d’obtenir notre texte centré, avec des espaces avant et après sur toute la longueur du terminal, et surtout quelle que soit la longueur du terminal !
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 :