Skip to content
Tags

,

Comparatif grep, ack et grin

17 février 2012

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 paquet ack-grep. La commande est aussi ack-grep. Le changement de nom est dû au fait qu’il existait déjà un paquet du nom de ack. Dans la suite de l’article, on mentionnera simplement ack ;
  • grin : outil écrit en Python ayant le même but que ack. 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é avec pip 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 de grin --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

From → Debian

2 commentaires
  1. Damien permalink

    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 »).

  2. 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.

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s

%d blogueurs aiment cette page :