Comparatif grep, ack et grin
Sur le ring, grep
dominait et le public l’acclamait. Mais deux petits nouveaux, ack
et grin
, avaient enfilé leurs plus belles tenues de super-stars du catch pour lui ravir sa ceinture de champion et le cœur des développeurs. Vont-ils y arriver ? C’est ce que vous saurez en lisant cet article !
Parfois, un programmeur veut connaître l’ensemble des occurrences d’un symbole dans un programme (une variable, une classe, etc.). La solution la plus évidente est d’utiliser grep
mais, pour peu qu’il y ait d’autres fichiers dans le répertoire du projet, de nombreux faux positifs vont apparaître, rendant le travail d’analyse fastidieux. Par exemple, s’il existe des copies créées automatiquement par un éditeur de texte, les fichiers des dépôts (.svn
, .hg
, fichier binaire, etc.). À Yaal, nous utilisons Python. Avec virtualenv, les fichiers de dépôt, les fichiers .pyc produits automatiquement, la documentation, etc. les résultats de grep
sont plus ou moins truffés de faux positifs.
Il est bien évidemment possible d’exclure ce genre de fichier en multipliant les commandes (| grep -v a_exclure
). Mais l’augmentation des lignes à éliminer rend la commande toujours moins pratique. Pour contourner cela, la suite logique est de transformer la commande en un script shell. Cependant, d’autres programmes comme ack
et grin
visent déjà à résoudre ce problème. L’objectif de cet article est de montrer quelques spécificités de chaque outil.
Les différents outils
Voici une présentation succincte des trois adversaires :
grep
: l’outil Unix classique qui servira de référence ;ack
: outil écrit en Perl visant à faire des recherches, orienté pour des développeurs. Ce logiciel est disponible dans Debian et dérivées par l’intermédiaire du paquetack-grep
. La commande est aussiack-grep
. Le changement de nom est dû au fait qu’il existait déjà un paquet du nom deack
. Dans la suite de l’article, on mentionnera simplementack
;grin
: outil écrit en Python ayant le même but queack
. Il n’est pas disponible dans Debian. Étant donné qu’il est disponible dans Pypi (les dépôts de paquets Python, équivalant au CPAN de Perl), grin peut être installé avecpip install grin
. Le paquet python-pip doit être préalablement installé. Puisqu’il n’y a pas de page de manuel, la documentation à utiliser est celle degrin --help
et la page de documentation sur le dépôt Python.
Que le match commence !
Usage
Recherche dans un fichier
grep MOTIF FICHIER ack-grep MOTIF FICHIER grin MOTIF FICHIER
Dans chaque cas, MOTIF peut être une expression rationnelle (par exemple [fF]év
vaut fév
ou Fév
).
Voici un exemple de résultats que l’on peut obtenir :
stephane@foehn:~/src/strdatetime$ grep [Ff]év translation.py u"janv", u"févr", u"mars", u"janvier", u"février", u"mars", u"Janvier", u"Février", u"Mars", stephane@foehn:~/src/strdatetime$ ack-grep [Ff]év translation.py u"janv", u"févr", u"mars", u"janvier", u"février", u"mars", u"Janvier", u"Février", u"Mars", stephane@foehn:~/src/strdatetime$ grin [Ff]év translation.py translation.py: 19 : u"janv", u"févr", u"mars", 26 : u"janvier", u"février", u"mars", 30 : u"Janvier", u"Février", u"Mars",
La sortie de grep
et d’ack
sont identiques, alors que grin
fournit le nom du fichier et le numéro de lignes.
Recherche récursive
La plupart du temps, l’objectif est de chercher dans tout une arborescence de répertoire. C’est le fonctionnement par défaut pour ack
et grin
.
grep -r MOTIF . #ou rgrep ack-grep MOTIF grin MOTIF
La sortie produite par grep
et ack
évolue pour ajouter le nom du fichier et les lignes. grep
met tout sur une ligne alors qu’ack
a une sortie identique à celle de grin
. grin
produit une sortie toujours structurée de la même manière (cf. l’exemple du paragraphe précédent).
Alors que la différence de vitesse lors de l’analyse d’un seul fichier ne se sent pas, elle devient sensible lors d’une recherche récursive. Quelques tests rapides (et sans prétention d’exhaustivité ou de représentativité) donnent des résultats bien plus lents pour ack
et grin
.
grep : 0,3 s
ack-grep : 1,7 s
grin : 2,8 s
Cependant, ajouter des tubes pour exclure des résultats obtenus avec grep
dans le but d’éliminer des résultats (et donc arriver à un résultat comparable avec ce qui est obtenu avec ack
ou grin
) augmentera le temps passé et donc réduira l’écart avec les deux autres outils.
Recherche insensible à la casse
grep -i MOTIF FICHIER ack-grep -i MOTIF FICHIER grin -i MOTIF FICHIER
Même syntaxe que dans les cas précédents, juste l’option -i
en plus. Aucune surprise ici. Pas la peine d’en rajouter. Passez au paragraphe suivant. Faut-il vraiment que j’arrête d’écrire ce paragraphe pour vous arrêtiez de le lire ?!
Recherche en ignorant certains types de fichiers
Cette fonctionnalité est très utile pour ne pas chercher dans des fichiers sans intérêt :
grep -r --exclude=*.sh --exclude=*.h "interpreted as " . ack-grep --type=nohh --type=noshell "interpreted as " grin --skip-exts .h,.sh "interpreted as "
Ici, on exclut les fichiers d’en-tête de code C (*.h
) et les scripts shell (*.sh
). grep
et grin
recherchent directement une correspondance de motif alors qu’ack
permet des exclusions de types ou d’extension. Les types sont basés sur les noms d’extension de fichiers (avec la correspondance de motif). Par exemple, le type shell
correspond à .sh
, .bash
, .csh
, .tcsh
, .ksh
et .zsh
. La définition d’un type de fichier est conçu pour être facilement extensible.
Par défaut, ack
et grin
exclut les répertoires et les fichiers sans intérêt. L’option d’exclusion permet d’en ajouter de nouveaux. La liste des fichiers exclus par défaut n’est pas identique entre ack
et grin
donc les résultats peuvent différer un peu. Par exemple, c’est le cas des fichiers .texi
, exclus par ack
, mais pas par grin
.
Recherche en ignorant certains répertoires
grep -r --exclude-dir DIR1 --exclude-dir DIR2 MOTIF . ack-grep --ignore-dir DIR1 --ignore-dir DIR2 MOTIF grin --skip-dirs DIR1,DIR2 MOTIF
Ces commandes permettent d’exclure DIR1 et DIR2 de la recherche. grin
ajoute les répertoires enlevés à la liste des répertoires qu’il exclut automatiquement.
Ajout des lignes de contexte
Les options sont toutes identiques et ont le même comportement.
-A, --after-context -B, --before-context -C, --context #avant et après
Comme à chaque fois, grin
affichage systématiquement le numéro des lignes.
Affichage limité aux fichiers correspondants
grep -r -l MOTIF. ack-grep -l MOTIF grin -l MOTIF
C’est donc l’option -l
ou --files-with-matches
à chaque fois.
Affichage limité aux fichiers non correspondants
grep -r -L MOTIF . ack-grep -L MOTIF # = -l -v #rien pour grin !
L’option -L
(ou --files-without-matches
) permet de n’afficher que les fichiers sans l’occurence MOTIF pour grep
et ack
. grin
a bien une option de ce nom mais son comportement est complètement différent. L’aide de la commmande signale que ce paramètre permet d’afficher le nom du fichier avec la correspondance. Ce qui est le comportement par défaut. Je ne comprend pas l’intérêt de cette option, trompeuse de surcroît.
Inversion de la sélection
grep -v MOTIF FICHIER ack-grep -v MOTIF FICHIER #rien pour grin !
Pas d’option équivalente à -v
pour grin
. Rien ! Nada ! Zéro ! Que dalle ! ∅ !
Intégration avec un éditeur
ack
dispose d’un paramètre --column
pour qu’un éditeur de texte se place directement sur la première occurence.
La documentation d’ack
précise que l’outil est intégrable avec Vim, Emacs et TextMate. Je n’ai testé aucune de ces possibilités.
grin
dispose d’un paramètre --emacs
qui affiche le nom du fichier, le numéro de la ligne et son contenu sur la même ligne pour faciliter l’analyse « par exemple avec emacs » :
stephane@foehn:~/src/strdatetime$ grin Fév ./tests.py: 122 : d_fr = strdatetime.strdatetime(u"Févr", "%b", lang="fr") 136 : d_fr = strdatetime.strdate(u"Févr", "%b", lang="fr") ./translation.py: 30 : u"Janvier", u"Février", u"Mars", stephane@foehn:~/src/strdatetime$ grin --emacs Fév ./tests.py:122: d_fr = strdatetime.strdatetime(u"Févr", "%b", lang="fr") ./tests.py:136: d_fr = strdatetime.strdate(u"Févr", "%b", lang="fr") ./translation.py:30: u"Janvier", u"Février", u"Mars",
Je suis très dubitatif sur le choix de nommage des options dans grin mais cette option me semble vraiment intéressante.
Configuration
Il est possible d’enregistrer des préférences pour qu’elles soient utilisées à chaque fois.
grep
est le plus limité. Si l’utilisateur enregistre un fichier FICHIER avec tous les motifs de noms de fichier (possibilité d’utiliser les caractères jokers de l’interprète de commande) qu’il veut exclure, il peut les faire prendre en compte en utilisant --exclude-from=FICHIER
. C’est limité mais cette fonctionnalité reste probablement sous-exploitée.
ack
prend en compte un fichier de configuration .ackrc
dans lequel on peut ajouter les options à lancer systématiquement. Simple et efficace. Certains paramètres sont aussi accessibles grâce à des variables d’environnement.
grin
utilise une variable d’environnement $GRIN_ARGS
. Elle doit valoir une chaîne de caractère qui inclut les paramètres à ajouter par défaut. Fonctionnel mais moins élégant que le fichier de configuration d’ack
.
Conclusion
Pour une utilisation dans le cadre de développement logiciel, ack
me semble le plus adapté. Par contre, le fait qu’il n’affiche pas toutes les informations sur la même ligne empêche sa sortie d’être réutilisable facilement. grin
me semble moins bon mais évite cet écueil (avec --emacs
) et affiche toujours les lignes, ce qui peut s’avérer assez pratique.
grep
est plus généraliste et possède plus d’options. Il reste donc indispensable et a l’avantage d’être toujours disponible sur le système, ce qui n’est pas le cas des deux autres.
Je n’ai fait qu’aborder les fonctionnalités qui me semblaient les plus courantes, chacun en possède d’autres et sont à portée de man
. Explorez-les !
Versions utilisées :
grep (GNU grep) 2.10
ack-grep 1.92
grin 1.2.1
Il y a également Rak côté Ruby, pour que le tableau soit complet : https://github.com/danlucraft/rak
Sinon, dans ta conclusion, je trouve que l’on ne voit pas très clairement pouquoi tu recommandes ack plus que grin (« grin me semble moins bon est vague »).
Je considère ack préférable à grin en particulier pour :
– la possibilité de trouver les fichiers qui ne correspondent pas
– la possibilité d’enregistrer ses préférences dans le fichier .ackrc
La seule chose qui sauvait grin était la possibilité offerte par l’option –emacs. Cependant, depuis la publication de l’article, je me suis rendu compte que ack a le même comportement s’il est réutilisé par un autre outil. Par exemple:
$ ack-grep Fév |grep 122
tests.py:122: d_fr = strdatetime.strdatetime(u »Févr », « %b », lang= »fr »)
Donc, de mon point de vue, il n’y a plus de raison à utiliser grin.