Exécutez `grep` en excluant un fichier dans un chemin spécifique

12

Je souhaite exclure le fichier ./test/main.cppde ma recherche.

Voici ce que je vois:

$ grep -r pattern --exclude=./test/main.cpp
./test/main.cpp:pattern
./lib/main.cpp:pattern
./src/main.cpp:pattern

Je sais qu'il est possible d'obtenir la sortie que je veux en utilisant plusieurs commandes dans un arrangement de tuyaux et de filtres, mais y a-t-il des citations / échappements qui feront grepcomprendre ce que je veux en natif?

nobar
la source
Une solution basée sur le filtrage de la sortie n'est pas bien mise à l'échelle car elle recherche inutilement le fichier avant d'exclure les résultats associés. Le problème est amplifié si je veux exclure des répertoires entiers (avec --exclude-dir). C'est pourquoi je voudrais que grep effectue nativement l'exclusion.
nobar
1
--exclude spécifie que glob n'est pas un chemin
PersianGulf

Réponses:

6

grep ne peut pas faire cela pour un fichier dans un certain répertoire si vous avez plus de fichiers du même nom dans différents répertoires, utilisez plutôt find:

find . -type f \! -path './test/main.cpp' -exec grep pattern {} \+

MichalH
la source
Pourquoi vous échappez-vous \!et \+? Il semble fonctionner correctement sans les contre-obliques.
nobar
@nobar J'y suis habitué car certains caractères sont des mots-clés shell donc vous ne serez jamais surpris car rien ne peut se produire s'ils sont échappés.
MichalH
" grepne peut pas faire cela, utilisez findplutôt" - parfait.
nobar
4

Je ne pense pas que ce soit possible avec GNU grep. Mais vous n'avez pas besoin de tuyaux.

Avec find:

find . ! -path ./test/main.cpp -type f -exec grep pattern {} +

Avec zsh:

grep pattern ./**/*~./test/main.cpp(.)

(exclut les fichiers cachés, tout aussi bien pour exclure les .git, .svn ...).

Stéphane Chazelas
la source
2

Je pourrais écrire un livre: "L'art perdu de xargs". Le find ... -exec … ';lance un grep pour chaque fichier (mais pas la variante avec -exec … +). Eh bien, nous perdons des cycles CPU ces jours-ci, alors pourquoi pas, non? Mais si les performances, la mémoire et l'alimentation sont un problème: utilisez xargs:

find . -type f \! -path 'EXCLUDE-FILE' -print0 | xargs -r0 grep 'PATTERN'

De GNU find« s de -print0se NUL-terminate sa production et xargs» les -0honneurs options que le format en entrée. Cela garantit que quels que soient les personnages amusants de votre fichier, le pipeline ne sera pas confondu. L' -roption s'assure qu'il n'y a pas d'erreur au cas où findrien ne serait trouvé.

Remarque, vous pouvez maintenant faire des choses comme:

find . -type f -print0 | grep -z -v "FILENAME EXCLUDE PATTERN" | 
  xargs -r0 grep 'PATTERN'

GNU grep -zfait la même chose que xargs -0.

Otheus
la source
3
Quelques notes intéressantes, mais je ne suis pas sûr que vous ayez raison sur le problème de performances. Si je comprends bien, cela find -exec (cmd) {} +fonctionne de la même manière que xargset find -exec (cmd) {} \;fonctionne de la même manière que xargs -n1. En d'autres termes, votre déclaration n'est correcte que si la \;version est utilisée.
nobar
3
La tuyauterie dans xargsest moins efficace que l'utilisation -exec … +(quoique marginalement). Aucune des réponses ici ne mentionne même -exec … \;.
Gilles 'SO- arrête d'être méchant'
1
Eh bien, s - t. Je sors avec moi. Merci pour les commentaires et corrections. Je pensais que le \ + était une faute de frappe. Oh regardez, -exec ... +ajouté en janvier 2005. Ouais, je ne suis pas obsolète ... du tout.
Otheus
2

Si vos findsupports -pathont été ajoutés à POSIX en 2008 mais manquent toujours dans Solaris:

find . ! -path ./test/main.cpp -type f -exec grep pattern /dev/null {} +
cuonglm
la source
1
Je ne pense pas que cela fonctionnera car nobar veut main.cpp dans d'autres répertoires
Eric Renouf
1
votre modèle n'exclura-t-il pas également main.cpp de tous les autres répertoires? Ce ne serait pas souhaitable
Eric Renouf
@EricRenouf: Oh, mon erreur, une mauvaise lecture. Mis à jour ma réponse.
cuonglm
@Gilles: Pourquoi -pathpas POSIX?
cuonglm
Ah, désolé, mon erreur, il a été ajouté en 2008. Toujours absent de Solaris.
Gilles 'SO- arrête d'être méchant'
1

Pour mémoire, voici l'approche que je préfère:

grep pattern $(find . -type f ! -path './test/main.cpp')

En gardant le grepau début de la commande, je pense que c'est un peu plus clair - en plus cela ne désactive pas grepla surbrillance des couleurs. Dans un sens, l'utilisation finddans une substitution de commande n'est qu'un moyen d'étendre / remplacer le sous-ensemble (limité) de recherche de fichiers de grepla fonctionnalité de.


Pour moi, la find -execsyntaxe est un peu mystérieuse. Une complexité avec find -execest le besoin (parfois) d'échapper à divers caractères (notamment si \;est utilisé sous Bash). Juste pour mettre des choses dans des contextes familiers, les deux commandes suivantes sont fondamentalement équivalentes:

find . ! -path ./test/main.cpp -type f -exec grep pattern {} +
find . ! -path ./test/main.cpp -type f -print0 |xargs -0 grep pattern

Si vous souhaitez exclure des sous - répertoires , il peut être nécessaire d'utiliser un caractère générique. Je ne comprends pas complètement le schéma ici - parlez des arcanes :

grep pattern $(find . -type f ! -path './test/main.cpp' ! -path './lib/*' )

Une autre note pour généraliser les findsolutions basées sur l'utilisation dans les scripts : La grepligne de commande doit inclure l' option -H/ --with-filename. Sinon, cela modifiera la mise en forme de la sortie dans le cas où il se trouve qu'il n'y a qu'un seul nom de fichier dans les résultats de la recherche find. Ceci est remarquable car il ne semble pas nécessaire si vous utilisez grepla recherche de fichiers native de (avec l' -roption).

... Encore mieux, cependant, est d'inclure /dev/nullcomme premier fichier à rechercher. Cela résout deux problèmes:

  • Il garantit que s'il y a un fichier à rechercher, greppense qu'il y en a deux et utilise le mode de sortie à fichiers multiples.
  • Il garantit que s'il n'y a pas de fichiers à rechercher, greppensera qu'il y a un fichier et ne se bloque pas en attente sur stdin.

La réponse finale est donc:

grep pattern /dev/null $(find . -type f ! -path './test/main.cpp')
nobar
la source
Vous ne devez pas utiliser la sortie de finddans une substitution de commande. Cela se casse s'il existe des noms de fichiers contenant des espaces ou d'autres caractères spéciaux. Utilisation find -exec, il est robuste et facile à utiliser.
Gilles 'SO- arrête d'être méchant'
@ Gilles: Très bon point - la sortie pourrait également dépasser les limites de taille de ligne de commande de certains programmes. Caveat emptor.
nobar
Pouah. la syntaxe «trouver» est terriblement difficile. '-o' est un opérateur "ou" (également '-ou' sous Linux), mais son utilisation typique (par exemple avec '-prune') ne correspond pas conceptuellement à la notion d'un ou logique. C'est un ou fonctionnel plutôt que logique.
nobar
Une autre façon d'exclure des sous - répertoires sur la base correspondant à un nom: find -iname "*target*" -or -name 'exclude' -prune. Eh bien, cela fonctionne en quelque sorte - le répertoire élagué sera répertorié, mais pas recherché. Si vous ne voulez pas qu'il soit répertorié, vous pouvez ajouter une sorte de redondance! -name 'exclude'
nobar