Comment rechercher des fichiers contenant deux mots différents?

14

Je cherche un moyen de rechercher des fichiers où deux instances de mot existent dans le même fichier. J'ai utilisé ce qui suit pour effectuer mes recherches jusqu'à présent:

find . -exec grep -l "FIND ME" {} \;

Le problème que je rencontre est que s'il n'y a pas exactement un espace entre "FIND" et "ME", le résultat de la recherche ne donne pas le fichier. Comment puis-je adapter l'ancienne chaîne de recherche où les mots "FIND" et "ME existent dans un fichier par opposition à" FIND ME "?

J'utilise AIX.

Chad Harrison
la source
1
Les mots existent-ils n'importe où dans le fichier ou sont-ils toujours sur la même ligne?
Sobrique
L'intention était la même ligne.
Chad Harrison
Une alternative, si les mots sont sur la même ligne, consiste à utiliser une expression régulière avec grep -E/ egrepqui décrit tous les modèles qui vous intéressent (et à utiliser +au lieu de ;si votre recherche est prise en charge +.
MattBianco

Réponses:

21

Avec les outils GNU:

find . -type f  -exec grep -lZ FIND {} + | xargs -r0 grep -l ME

Vous pouvez faire de façon standard:

find . -type f -exec grep -q FIND {} \; -exec grep -l ME {} \;

Mais cela exécuterait deux greps par fichier. Pour éviter d'exécuter autant de greps tout en restant portable tout en autorisant tout caractère dans les noms de fichiers, vous pouvez faire:

convert_to_xargs() {
  sed "s/[[:blank:]\"\']/\\\\&/g" | awk '
    {
      if (NR > 1) {
        printf "%s", line
        if (!index($0, "//")) printf "\\"
        print ""
      }
      line = $0
    }'
    END { print line }'
}

find .//. -type f |
  convert_to_xargs |
  xargs grep -l FIND |
  convert_to_xargs |
  xargs grep -l ME

L'idée étant de convertir la sortie de finddans un format adapté aux xargs (qui attend un blanc (SPC / TAB / NL, et les autres blancs de votre environnement local avec quelques implémentations de xargs)) liste séparée de mots où les guillemets simples, doubles et les contre-obliques peuvent échapper aux blancs et les uns aux autres).

En règle générale, vous ne pouvez pas post-traiter la sortie de find -print, car elle sépare les noms de fichier par un caractère de nouvelle ligne et n'échappe pas aux caractères de nouvelle ligne qui se trouvent dans les noms de fichier. Par exemple, si nous voyons:

./a
./b

Nous n'avons aucun moyen de savoir s'il s'agit d'un fichier appelé bdans un répertoire appelé a<NL>.ou s'il s'agit des deux fichiers aet b.

En utilisant .//., car //ne peut pas apparaître autrement dans un chemin de fichier en sortie par find(car il n'y a pas de répertoire avec un nom vide et /n'est pas autorisé dans un nom de fichier), nous savons que si nous voyons une ligne qui contient //, alors c'est la première ligne d'un nouveau nom de fichier. Nous pouvons donc utiliser cette awkcommande pour échapper à tous les caractères de nouvelle ligne, sauf ceux qui précèdent ces lignes.

Si nous prenons l'exemple ci-dessus, findsortirait dans le premier cas (un fichier):

.//a
./b

Quel awk échappe à:

.//a\
./b

Cela le xargsconsidère donc comme un argument. Et dans le deuxième cas (deux fichiers):

.//a
.//b

Ce qui awkresterait tel quel , xargsvoit donc deux arguments.

Stéphane Chazelas
la source
Pourquoi ne pas utiliser find ... -print0et à la grep --nullplace?
ébloui le
@ stupéfait, je ne sais pas ce que tu veux dire. grep --null(aka -Z) est utilisé dans le premier mais est une extension GNU. -print0(une autre extension GNU) n'aiderait pas ici.
Stéphane Chazelas
Merci. Je voudrais encapsuler votre code shell dans un script qui prend le répertoire de recherche comme argument de la ligne de commande. Je ne suis pas encore très sûr de ce que cela .//.signifie, et je me demande comment je peux le modifier pour accepter un argument de la ligne de commande, par exemple $1?
Tim
Merci. Dans votre commande, faut-il utiliser -print0avec findet -0avec xargs?
Tim
@Tim, je ne sais pas ce que tu veux dire. Je n'utilise find -print0nulle part dans ma réponse.
Stéphane Chazelas
8

Si les fichiers se trouvent dans un seul répertoire et leur nom ne contiennent pas d' espace, tabulation, nouvelle ligne, *, ?ni [caractères et ne commencent pas par -ni ., cela obtenir une liste des fichiers contenant ME, puis réduire que jusqu'à ceux qui contiennent également FIND.

grep -l FIND `grep -l ME *`
user45529
la source
CECI a besoin de plus de votes positifs !! Bien plus élégante que la réponse "acceptée". A travaillé pour moi.
roblogic
Je viens de le faire grep -l CategoryLinearAxis `grep -l labelJsFunction *`en recherchant des fichiers qui contiennent les deux attributs. Quelle façon parfaite de le faire. +1
WEBjuju
3

Avec awkvous pouvez également exécuter:

find . -type f  -exec awk 'BEGIN{cx=0; cy=0}; /FIND/{cx++}
/ME/{cy++}; END{if (cx > 0 && cy > 0) print FILENAME}' {} \;

Il utilise cxet cypour compter les lignes correspondant FINDet respectivement ME. Dans le ENDbloc, si les deux compteurs> 0, il imprime le FILENAME.
Ce serait plus rapide / plus efficace avec gnu awk:

find . -type f  -exec gawk 'BEGINFILE{cx=0; cy=0}; /FIND/{cx++}
/ME/{cy++}; ENDFILE{if (cx > 0 && cy > 0) print FILENAME}' {} +
don_crissti
la source
2

Ou utilisez egrep -eou grep -Eaimez ceci:

find . -type f -exec egrep -le '(ME.*FIND|FIND.*ME)' {} \;

ou

find . -type f -exec grep -lE '(ME.*FIND|FIND.*ME)' {} +

La commande +make find (si elle est prise en charge) ajoute plusieurs noms de fichier (chemin) comme arguments à la commande en cours d' -execédition. Cela enregistre les processus et est beaucoup plus rapide que celui \;qui appelle la commande une fois pour chaque fichier trouvé.

-type f ne correspond qu'aux fichiers, pour éviter de grepping sur un répertoire.

'(ME.*FIND|FIND.*ME)'est une expression régulière correspondant à toute ligne contenant "ME" suivi de "FIND" ou "FIND" suivi de "ME". (guillemets simples pour empêcher le shell d'interpréter des caractères spéciaux).

Ajoutez un -ià la grepcommande pour la rendre insensible à la casse.

Pour ne faire correspondre que les lignes où "FIND" précède "ME", utilisez 'FIND.*ME'.

Pour exiger des espaces (1 ou plus, mais rien d'autre) entre les mots: 'FIND +ME'

Pour autoriser des espaces (0 ou plus, mais rien d'autre) entre les mots: 'FIND *ME'

Les combinaisons sont infinies avec des expressions régulières, et à condition que vous soyez intéressé à faire des correspondances uniquement ligne par ligne, egrep est très puissant.

MattBianco
la source
La plupart des greps ne prennent-ils pas en charge "-r"? Cela éliminerait la «recherche», mais il pourrait y avoir des sockets ou d'autres fichiers non simples dans l'arborescence recherchée.
stolenmoment
OP utilise AIX et avait finddans la question.
MattBianco
0

En regardant la réponse acceptée, elle semble plus complexe qu'elle ne devrait l'être. Versions GNU de findet grepet xargssupport des chaînes terminées par NULL. C'est aussi simple que:

find . -type f -print0 | xargs -0 grep -l --null FIND | xargs -0 grep -l ME

Vous pouvez modifier votre findcommande pour filtrer sur les fichiers que vous souhaitez, et cela fonctionne avec les noms de fichiers contenant n'importe quel caractère; sans la complexité supplémentaire de l' sedanalyse. Si vous souhaitez poursuivre le traitement des fichiers, ajoutez-en un autre --nullau derniergrep

find . -type f -print0 | xargs -0 grep -l --null FIND | xargs -0 grep -l --null ME | xargs -0 echo

Et, en fonction:

find_strings() {
    find . -type f -print0 | xargs -0 grep -l --null "$1" | xargs -0 grep -l "$2"
}

Évidemment, utilisez la réponse acceptée si vous n'exécutez pas de versions GNU de ces outils.

ébloui
la source
1
--null, --print0, -0Sont toutes les extensions GNU. Bien que certains d'entre eux se trouvent dans d'autres implémentations de nos jours, ils ne sont toujours pas portables et ne sont pas dans la norme POSIX ou Unix.
Stéphane Chazelas