Rolinh

Rolinh' release

Colorez Votre Shell

Il y a maintenant un peu plus d’un an, Teo Mrnjavac, membre de la communauté KDE et un des auteurs d’Amarok, avait écrit un article intéressant sur des utilitaires en couleur pour le terminal.

Cet article va dans le même sens car pour moi, un shell personnalisé est tout de suite plus attrayant et l’utilisation de couleurs, lorsqu’elles le sont à bon escient, y contribue. L’autre aspect important et l’interactivité que permet un shell bien configuré, par le biais de la complétion des commandes par exemple. À ce titre, je ne saurais plus me passer de zsh (voir cet ancien article notamment).

Le shell

Un shell bien personnalisé, ça commence par un PS1 (invite de commande) agréable. Chez moi, il ressemble à ça:

C’est donc un classique utilisateur@hôte avec le chemin du dossier courant. Ce dernier se trouve d’ailleurs raccourci si nécessaire:

Il change également de couleur en fonction des permissions du dossier courant:

Un autre point sympa: si je passe en root, cela se remarque tout de suite:

A noter également que la couleur du nom de l’ĥôte change en fonction de l’hôte. Ainsi, ça permet de savoir encore plus facilement sur quelle machine on se trouve (et éviter d’éteindre par mégarde un serveur plutôt que son poste de travail par exemple…). Merci à Alex à qui j’ai piqué la configuration de base il y a quelques années.

Mon shell intègre également un autre prompt, situé à l’extrême droite, lorsque le dossier courant est un dépôt git ou mercurial (je n’utilise pas svn, bazaar ou autre donc n’en tiens pas compte). Il permet de visualiser en un coup d’oeil l’état du dépôt: la branche courante, le nombre de nouveaux fichiers, si des changements ont été apportés, s’ils ont été commités, s’il y a besoin de compléter un merge, etc.

Pour plus d’information à ce sujet, je vous laisse vous référez à cet article que j’ai publié il y a plus d’un an en arrière.

Cela rend tout de suite le terminal plus agréable un prompt du genre, pas vrai? Oui mais, il y a moyen de faire encore mieux. Je ne vais pas parler ici de toutes les astuces que l’on peut utiliser via le ~/.zshrc car cela serait d’une part trop long et que ce n’est pas le sujet de cet article. En revanche, puisque cet article traite de couleur et de shell, voici une astuce que je trouve fort sympa. Peut-être que vous connaissez le shell fish (friendly interactive shell)? C’est un shell qui possède beaucoup de fonctionnalités très intéressantes (complétion, suggestions basées sur l’historique, utilisation intensive de couleurs, etc.), la plupart activée par défaut. Exemple de complétion dans un dépôt git:

On peut remarquer sur cette dernière image une chose intéressante: les commandes valides sont en bleu alors que les commandes non-valides sont en rouge. On voit également que les chaînes sont colorées en jaune. Plutôt sympa non? Personnellement, j’adore! Comme je n’utilise pas le shell fish mais zsh, j’utilise un plugin dénommé zsh-syntax-highlighting qui se charge d’effectuer le même rendu au niveau des couleurs pour zsh. Exemple de rendu (les exécutables valides étant ici en vert):

À propos de commandes valides ou non, j’ai également une autre personnalisation de mon prompt que je trouve intéressante: l’indication du code de sortie d’une commande lorsqu’elle retourne sur une erreur. Exemple:

Avec tout ça, on se retrouve déjà avec quelque chose d’intéressant. Cependant, à côté du shell à proprement parler, il y a également les utilitaires en ligne de commande que l’on utilise qui peuvent être paramétrés ou même remplacés par des équivalents plus colorés. Je vais donc tâcher d’en faire ici une liste non exhaustive.

ccze

CCZE permet de colorer des fichiers de logs. J’utilise cette fonction dans mon ~/.zshrc afin quand je dois débloquer une situation en consultant des logs.

1
2
3
logtail () {
            tail -f $1 | ccze -A
}

Ce qui donne quelque chose du genre:

Merci à Frank qui m’avait fourni l’astuce.

ack

ack est une alternative à grep(1), écrit en Perl, qui se trouve être particulièrement pratique pour trouver ce que l’on cherche parmi des fichiers source par exemple:

git et mercurial

Il ne s’agit pas ici d’alternative mais simplement de configurer ces gestionnaires de version afin que le retour de leur commandes respectives soit un peu plus coloré. Pour git, j’ai ceci dans mon ~/.gitconfig:

1
2
3
4
5
6
[color]
        branch = auto
        diff = auto
        interactive = auto
        status = auto
        ui = auto

Et ceci dans mon ~/.hgrc pour mercurial:

1
2
3
4
5
6
7
8
9
[extensions]
color                   =

[color]
status.modified = blue bold
status.added    = green
status.removed  = red
status.unknown  = black bold
status.deleted  = red bold

C’est aussi tout de suite plus agréable:

Si vous n’utilisez ni git ni mercurial et que votre gestionnaire de version ne supporte pas les couleurs (svn par exemple), vous pouvez toujours vous rabattre sur colordiff afin d’avoir au moins un diff(1) en couleur.

htop

On tombe cette fois dans un grand classique. htop est un bon remplaçant à top(1) (même si la qualité de son code source laisse un peu à désirer…):

cdu

Vous connaissez certainement du(1), cet utilitaire qui donne une estimation de l’utilisation de l’espace disque occupé par le dossier donné en argument? cdu en est une alternative en couleur:

C’est pratique pour un coup d’oeil rapide bien que je trouve ncdu, une version ncurse (sans couleurs) de du(1), plus pratique à cet égard.

dfc

C’est presque un peu inutile que je le présente sur ce blog mais dfc fait selon moi une excellente alternative à df(1) (qui se charge de plus que simplement ajouter de la couleur d’ailleurs):

Si l’outil ne vous plait pas, il existe des alternatives telles que pydf ou cdf.

freec

Frank, que j’ai déjà cité dans cet article, a écrit un programme alternatif à free(1): freec.

Autres

Je trouve la liste déjà relativement exhaustive mais il en existe encore d’autres.

  • ls++: La plupart des gens activent les couleurs pour la commande ls(1) via LS_COLORS. Personnellement, cela me suffit mais ceux qui en veulent plus peuvent passer par ce wrapper Perl.
  • colorgcc: Qui se révèle être un bon palliatif à gcc pour les utilisateurs habitués à compiler avec clang.
  • colormake: Pour colorer make(1).
  • vimpager ou encore une configuration de most ou less pour les pages de manuel.

Je suis sûr que vous saurez la compléter par des utilitaires que vous appréciez aussi. ;)

Un Dépôt Pour Ses Fichiers De Configuration Et Un Rakefile Pour Les Déployer

J’ai plusieurs articles plutôt conséquents en préparation (un tutoriel sur les Makefile ainsi qu’une explication très détaillée du fonctionnement de l’algorithme RSA avec implémentation en Ruby) mais comme je ne trouve pas vraiment le temps de les finir en ce moment, j’en profite pour publier un petit article.

Un ami m’a demandé aujourd’hui comment je gérais mes fichiers de configuration; ces fameux dotfiles, ou fichiers cachés, présents dans $HOME ou $XDG_CONFIG_HOME. La réponse est simple: via un dépôt git. Oui mais… comment les copier dans $HOME ensuite? Simplement via un script, ou plus exactement un Rakefile qui s’occupe de faire des liens symboliques vers les fichiers de mon dépôt. J’utilisais auparavant un script shell (assez moche d’ailleurs) mais bon, un script shell, même si ça rend parfois bien service, c’est pas beau.

Voici donc la procédure que j’utilise: Lorsque j’ai accès à une nouvelle machine, mon premier réflexe est d’installer mes outils indispensables, comme vim, zsh et git. Une fois ceci fait, je clone mon dépôt de fichiers de configuration, je me déplace dedans via un terminal, je lance un rake install et hop, je me sens comme chez moi. Simple non? Mais comment ça marche? Pour les non-familiers, rake est un outil similaire à make, qui fonctionne donc avec un Rakefile en lieu et place d’un Makefile. C’est un outil écrit en Ruby que l’on peut facilement installer via une gem.

Quel avantage par rapport à make me direz-vous? Et bien simplement le fait que l’on peut profiter de toute la puissance du langage haut-niveau qu’est Ruby pour définir des tâches qui sont les cibles de notre Rakefile et que l’on peut appeler des méthodes (comprenez fonction) définies en Ruby directement depuis ces tâches.

Dans le cas présent, seule une tâche est définie: install. Cette tâche s’occupe donc, comme je l’ai dit, de lier tous mes fichiers de configuration mais aussi de changer mon shell pour zsh, si ce dernier est installé sur le système, via des méthodes définies également dans le Rakefile. Bon assez parlé, le voici donc:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
require 'rake'

desc "links configuration files from this repository to $HOME"
task :install do
  dest = ENV['HOME']
  puts "Linking to ~"
  link_folder_content(dest)

  dest = dest + "/.config"
  puts "Linking to ~/.config"
  Dir.chdir("config")
  link_folder_content(dest)

  check_shell("/bin/zsh")
end

def link_folder_content(dest)
  replace_all = false

  Dir['*'].each do |file|
      next if %w[config Rakefile README linkconfig.sh].include? file

      file = "." + file if dest = ENV['HOME']

      if File.exist?(File.join(dest, "#{file}"))
          if replace_all
              replace_file(dest, file)
          else
              print "overwrite #{dest}/#{file}? [ynaq] "
              case $stdin.gets.chomp
              when 'a'
                  replace_all = true
                  replace_file(dest, file)
              when 'y'
                  replace_file(dest, file)
              when 'q'
                  exit
              else
                  puts "skipping #{dest}/#{file}"
              end
          end
      else
          link_file(dest, file)
      end
  end
end

def replace_file(dest, file)
  system %Q{rm -v "#{dest}/#{file}"}
  link_file(dest, file)
end

def link_file(dest, file)
  system %Q{ln -sv "$PWD/#{file.sub(/^(.?)/, "")}" "#{dest}/#{file}"}
end

def check_shell(shell)
  if ENV['SHELL'] != shell
      if command?(shell)
          system "chsh -s #{shell}"
      else
          puts "#{shell} not installed. Skipping."
      end
  else
      puts "#{shell} is already set as default shell."
  end
end

def command?(cmd)
  system %Q{which "#{cmd}" > /dev/null 2>&1}
end

Le script est certainement très facilement adaptable à votre besoin. Il est à noter que, dans mon dépôt, mes fichiers et dossiers ne sont pas précédés d’un “.”. La ligne 21 permet d’éviter de créer des liens vers des fichiers où dossiers pour lesquels ce n’est pas nécessaire. À vous donc de l’adapter au même titre que la tâche install.

Ah, et si mes fichiers de configuration vous intéressent, vous pouvez toujours accéder mon dépôt git ici

Monitorer Son Espace Disque Avec Dfc

Lorsque l’on doit gérer des serveurs, une des choses à prendre en compte est l’évolution du besoin en terme d’espace de stockage. En effet, ceux-ci évoluent au cours du temps et, afin de ne pas être pris de court, un bon administrateur systèmes se doit de mettre en place une solution lui permettant d’être informé de cette évolution. Cela lui permet de pouvoir planifier des mesures en conséquence, telles que l’achat de nouveaux disques durs ou d’un SAN.

Il existe de nombreuses solutions pour effectuer cette tâche mais si vous ne souhaitez pas prendre un bazooka pour tuer une mouche, alors dfc combiné à une tâche cron peut tout à fait faire l’affaire. En effet, depuis sa version 3.0.0, dfc permet l’export vers CSV et HTML, ce qui se révèle bien pratique pour cette tâche.

Dans cet article, je vais vous présenter les bases d’un script capable de relever le taux d’utilisation des disques d’une machine fonctionnant sous Linux, BSD ou OSX et d’envoyer le rapport par e-mail avec les versions en HTML et CSV en pièce jointe.

Il est d’abord nécessaire de définir quelques variables:

1
2
3
4
MAILADDRESS=john.doe@domain.tld
TMPFSREPORTHTML=/tmp/dfc-report.html
TMPFSREPORTCSV=/tmp/dfc-report.csv
BOUNDARY="========== BOUNDARY =========="

Il est également nécessaire de définir la fonction qui s’occupera effectivement de générer les données pour le rapport. À noter que si dfc n’est pas disponible sur le serveur pour une raison ou un autre, la commande df est appelée en remplacement:

1
2
3
4
5
6
7
8
9
10
fsusage() {
    echo "FILESYSTEM SPACE USAGE REPORT"
    if command -v dfc &>/dev/null; then
        echo "$(dfc -t -tmpfs,rootfs,devtmpfs -c never -disTW)"
        dfc -t -tmpfs,rootfs,devtmpfs -c always -disTWo -e html > $TMPFSREPORTHTML
        dfc -t -tmpfs,rootfs,devtmpfs -c never -disTWo -e csv > $TMPFSREPORTCSV
    else
        echo "$(df -Th)"
    fi
}

Étant donné que l’on souhaite envoyer le tout par email, une fonction qui génère son entête s’avère bien utile:

1
2
3
4
5
6
7
8
9
10
11
12
wheader() {
    echo "From: $(hostname -s)@$(hostname --domain)"
    echo "To: $MAILADDRESS"
    echo "Subject: Disk usage report for $(hostname)"
    if command -v dfc &>/dev/null; then
        echo "Mime-Version: 1.0"
        echo -e "Content-Type: Multipart/Mixed; boundary=\"$BOUNDARY\"\n"
        echo "--${BOUNDARY}"
    fi
    echo -e "Content-Type: text/plain\n"
    echo -e "Disk usage report for $(hostname) on $(date)\n"
}

Et puis une autre qui attache les pièces jointes (les fichiers CSV et HTML):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
mailattach() {
    if [ -f $TMPFSREPORTHTML ]; then
        echo "--${BOUNDARY}"
        echo "Content-Type: text/html; name="${TMPFSREPORTHTML}
        echo "Content-Disposition: attachment; filename="`basename ${TMPFSREPORTHTML}`
        echo
        cat $TMPFSREPORTHTML
        echo
    fi
    if [ -f $TMPFSREPORTCSV ]; then
        echo "--${BOUNDARY}"
        echo "Content-Type: text/csv; name="${TMPFSREPORTCSV}
        echo "Content-Disposition: attachment; filename="`basename ${TMPFSREPORTCSV}`
        echo
        cat $TMPFSREPORTCSV
        echo
    fi
    echo -e "--${BOUNDARY}--\n"
}

On a désormais tout ce qu’il faut. Il suffit donc simplement d’appeler ces fonctions et d’envoyer le tout par email. Bien évidemment, je considère ici que sendmail ou autre serveur d’envoi est installé et fonctionnel:

1
2
3
4
5
{
    wheader
    fsusage
    mailattach
} | sendmail -i -t

Pour bien faire, il faudrait générer un fichier de log mais je n’estime pas que des explications soient nécessaires pour cela.

EDIT: dfc se trouve sur ici s’il n’est pas déjà disponible en version >= 3.0.0 dans les dépôts de votre distribution.

Un Script Pour Mettre à Jour Ses Dépôt Git

Si vous êtes un peu comme moi, vous jonglez avec des dizaines de dépôts git. Ceux auxquels je participe se limitent à une vingtaine environ mais j’ai des dizaines de clones que je consulte à titre d’intérêt, comme ceux du noyau Linux, NetBSD ou autre Awesome WM. Bref, à ce jour, j’ai environ 21G de code source provenant de différents dépôts git sur mon laptop…

Évidemment, j’aime bien les tenir à jour. Donc, afin de me faciliter la tâche, j’ai écrit un simple script shell que j’ai placé à la racine du dossier contenant tous les clones de dépôt git. Il se charge de trouver tous les dossiers de dépôt git et d’y effectuer un git pull.

Le script est tout simple mais bon, comme ça peut éventuellement en arranger d’autres, je partage. ;–)

1
2
3
4
5
6
#!/bin/sh

for d in $(find . -type d -name ".git" | sed 's/\/.git\+$//' ); do
    echo "Current repository: $d";
    cd $d && git pull && cd - || exit 1
done

Si vous savez que vos clones ne se trouvent pas trop loin dans votre arborescence, vous pouvez limiter la recherche en profondeur du find via l’option maxdepth. Par exemple, pour limiter la recherche de dépôts git à une profondeur de 2, il faut modifier le script ainsi:

1
2
3
4
5
6
#!/bin/sh

for d in $(find . -maxdepth 2 -type d -name ".git" | sed 's/\/.git\+$//' ); do
    echo "Current repository: $d";
    cd $d && git pull && cd - || exit 1
done

Bien évidemment, ce script s’arrête s’il rencontre une erreur lors d’un pull sur un dépôt donc vous pouvez l’utiliser sans craintes.

Il va sans dire qu’une légère modification permet de l’utiliser pour d’autres types de dépôts. Par exemple, il suffit de remplacer “.git” par “.hg” et le git pull par un hg pull -u pour qu’il fonctionne avec les dépôts mercurial.

EDIT: Un lecteur m’a très justement fait remarquer qu’il existait la variable d’environnement $OLDPWD, accessible par l’alias -. J’ai donc simplifié le script en conséquence. Merci à lui.

Les Pointeurs Sur Fonction en C

Cela fait longtemps que je n’avais pas publié d’article sur mon blog. J’ai toutefois décidé de me remettre à l’écriture par le biais d’une brève explication sur les pointeurs sur fonction en C.

L’idée d’en parler m’est venue car, dans le cadre d’un projet en entreprise, j’ai été amené à mettre en place un protocole permettant de contrôler une application à distance via le réseau. Pour situer le contexte: l’application contrôlée est écrite en C, embarqué oblige, et elle tourne sur une carte comprenant un processeur ARM et un DSP. Pour les besoins du protocole, un daemon écoute sur une socket dédiée et reçoit de la part de clients des commandes sous la forme NOM_DE_LA COMMANDE [OPTIONS].

Le protocole en question doit pouvoir évoluer afin de prendre en charge des nouvelles commandes. Par conséquent, la contrainte que je me suis fixée est qu’il faut que l’ajout d’une nouvelle commande au protocole implique un minimum de changements au niveau du code. C’est afin de répondre à ce problème que j’ai eu recours aux pointeurs sur fonction.

Pour l’illustration, je vais donc donner un exemple, volontairement simplifié, en m’imaginant que je dois pouvoir traiter les commandes du protocole et appeler des fonctions internes à mon programme à cette fin.

En m’inspirant du design pattern “chaîne de responsabilité”, je vais écrire une fonction prenant en argument une commande du protocole avec ses options et qui se se charge d’appeler une fonction de traitement spécifique à ladite commande.

Avant de pouvoir faire cela, il est nécessaire de définir une structure contenant le nom d’une commande du protocole ainsi que sa fonction de traitement associée:

1
2
3
4
struct proto_cmd {
    const char *name;
    int (*process) (char *);
};

Si le premier élément de ma structure est un type très courant, les lecteurs non familier avec les pointeurs sur fonction sont certainement intrigués par le deuxième. Pour ceux-là, pas de panique, l’explication arrive.

Un protocole n’étant, en principe, pas constitué que d’une seule commande, il est également nécessaire d’avoir un moyen de stocker la liste des commandes. Cela peut se faire de plusieurs façons comme en utilisant le principe d’une liste chainée ou encore d’une table de hashage (bon, dans ce cas là le protocole devrait comporter un grand nombre de commandes pour que ça se justifie). Pour rester simple, je vais simplement stocker la liste des commandes dans un tableau:

1
2
3
4
5
struct proto_cmd cmd_table[NBCMD] = {
    {"quit", process_quit},
    {"start", process_start},
    {"stop", process_stop}
};

NBCMD correspond évidemment au nombre de commandes du protocole (seulement trois pour l’exemple ici).

A partir du moment où je possède une table avec la liste des commandes du protocole, il est facile d’écrire la fonction dont je vous parlais tout à l’heure:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int
select_cmd(char *cmd, char *args)
{
  int i;

  for (i = 0; i < NBCMD; i++)
      if (strcmp(cmd, cmd_table[i].name) == 0)
          return cmd_table[i].process(args);

    /*
     * Si on arrive la, alors cela veut dire que la commande ne fait pas partie
     * du protocole...
     */
     (void)fprintf(stderr, "Unknown command: %s\n", cmd);
     return 1;
}

La ligne 9 est celle qui revêt ici le plus d’intérêt. C’est en effet là que se fait l’appel à la fonction associée à la commande, ceci grâce au pointeur sur fonction. Il est à noter que le traitement des commandes se fait dans l’ordre des éléments du tableau. Si le protocole comporte de nombreuses commandes, il faudrait voir pour envisager une solution plus efficace.

Bien évidemment, il reste une chose à écrire: toutes les fonctions de traitement associées aux commandes. Dans ma table je les ai nommées process_quit, process_start et process_stop.

1
2
3
4
5
6
int
process_quit(char *args)
{
    /* on fait notre traitement de la commande "quit" ... */
    return 0;
}

Et ainsi de suite pour les autres commandes. Par conséquent, si l’on souhaite ajouter une commande au protocole, il suffit d’ajouter la commande en question dans la table et d’implémenter sa fonction process. Et c’est tout! Aucun autre changement n’est donc nécessaire dans le code. Sympa non?

Pour la dernière explication, je vais revenir sur la structure proto_cmd. On peut maintenant se rendre compte que, pour le deuxième élément, int correspond au type de retour de la fonction process, que (*process) correspond au pointeur sur la fonction et que (char *) s’associe à l’argument de la fonction process.

J’espère que cette brève explication illustrée était clair. Si ce n’est pas le cas, n’hésitez pas à me contacter par e-mail afin que je puisse modifier cet article en conséquence.