Rechercher récursivement un motif / texte uniquement dans le nom de fichier spécifié d'un répertoire?

16

J'ai un répertoire (par exemple, abc/def/efg) avec de nombreux sous-répertoires (par exemple ,:) abc/def/efg/(1..300). Tous ces sous-répertoires ont un fichier commun (par exemple, file.txt). Je souhaite rechercher une chaîne uniquement dans ce file.txtfichier à l'exclusion des autres fichiers. Comment puis-je faire ceci?

J'ai utilisé grep -arin "pattern" *, mais c'est très lent si nous avons de nombreux sous-répertoires et fichiers.

Rajesh Keladimath
la source

Réponses:

21

Dans le répertoire parent, vous pouvez utiliser findpuis exécuter grepuniquement sur ces fichiers:

find . -type f -iname "file.txt" -exec grep -Hi "pattern" '{}' +
Zanna
la source
2
Je suggère également de passer -Hà grepafin que, dans les cas où un seul chemin lui est transmis, ce chemin soit toujours imprimé (plutôt que simplement les lignes correspondantes du fichier).
Eliah Kagan
24

Vous pouvez également utiliser globstar.

Construire des grepcommandes avec find, comme dans la réponse de Zanna , est un moyen très robuste, polyvalent et portable de le faire (voir aussi la réponse de sudodus ). Et muru a publié une excellente approche de l'utilisation grepde l' --includeoption de . Mais si vous souhaitez utiliser uniquement la grepcommande et votre shell, il existe une autre façon de le faire - vous pouvez faire en sorte que le shell lui-même effectue la récursivité nécessaire :

shopt -s globstar   # you can skip this if you already have globstar turned on
grep -H 'pattern' **/file.txt

L' -Hindicateur fait grepafficher le nom du fichier même si un seul fichier correspondant est trouvé. Vous pouvez passer la -a, -iet des -ndrapeaux (de votre exemple) à grepaussi bien, si c'est ce dont vous avez besoin. Mais ne passez pas -rou -Rlorsque vous utilisez cette méthode. C'est le shell qui récursive les répertoires en développant le modèle glob contenant **, et nongrep .

Ces instructions sont spécifiques au shell Bash. Bash est le shell utilisateur par défaut dans Ubuntu (et la plupart des autres systèmes d'exploitation GNU / Linux), donc si vous êtes sur Ubuntu et ne savez pas quel est votre shell, c'est presque certainement Bash. Bien que les shells populaires prennent généralement en charge les **globs traversant les répertoires , ils ne fonctionnent pas toujours de la même manière. Pour plus d' informations, voir Stéphane Chazelas de excellente réponse à Le résultat de ls *, ls ** et *** ls sur Unix.SE .

Comment ça fonctionne

L' activation de l' option shell bash de globstar crée des **chemins de correspondance contenant le séparateur de répertoire ( /). C'est donc un glob récursif d'annuaire. Plus précisément, comme l' man bashexplique:

Lorsque l' option shell globstar est activée et que * est utilisé dans un contexte d'expansion de nom de chemin, deux * adjacents utilisés comme modèle unique correspondront à tous les fichiers et à zéro ou plusieurs répertoires et sous-répertoires. S'ils sont suivis d'un /, deux * adjacents ne correspondront qu'aux répertoires et sous-répertoires.

Vous devez être prudent avec cela, car vous pouvez exécuter des commandes qui modifient ou suppriment beaucoup plus de fichiers que vous n'en avez l'intention, surtout si vous écrivez **quand vous vouliez écrire *. (C'est sûr dans cette commande, qui ne change aucun fichier.) shopt -u globstarDésactive l'option shell globstar.

Il existe quelques différences pratiques entre globstar et find.

findest beaucoup plus polyvalent que globstar. Tout ce que vous pouvez faire avec globstar, vous pouvez aussi le faire avec la findcommande. J'aime globstar, et parfois c'est plus pratique, mais globstar n'est pas une alternative générale à find.

La méthode ci-dessus ne regarde pas à l'intérieur des répertoires dont les noms commencent par un .. Parfois, vous ne voulez pas récupérer de tels dossiers, mais parfois vous le faites.

Comme avec un glob ordinaire, le shell construit une liste de tous les chemins correspondants et les transmet comme arguments à votre commande ( grep) à la place du glob lui-même. Si vous avez tant de fichiers appelés file.txtque la commande résultante serait trop longue pour que le système s'exécute, la méthode ci-dessus échouera. En pratique, vous auriez besoin (au moins) de milliers de ces fichiers, mais cela pourrait arriver.

Les méthodes qui utilisent findne sont pas soumises à cette restriction, car:

  • La façon dont Zanna construit et exécute une grepcommande avec potentiellement de nombreux arguments de chemin. Mais si plus de fichiers sont trouvés que ce qui peut être répertorié dans un seul chemin, l' action +-terminated -execexécute la commande avec certains des chemins, puis l'exécute à nouveau avec quelques chemins supplémentaires, etc. Dans le cas d' greping pour une chaîne dans plusieurs fichiers, cela produit le comportement correct.

    Comme la méthode globstar couverte ici, cela imprime toutes les lignes correspondantes, avec des chemins ajoutés à chacune.

  • La voie de sudodus s'exécute grepséparément pour chaque file.txttrouvé. S'il y a beaucoup de fichiers, cela peut être plus lent que certaines autres méthodes, mais cela fonctionne.

    Cette méthode recherche les fichiers et imprime leurs chemins, suivis des lignes correspondantes le cas échéant. Il s'agit d'un format de sortie différent du format produit par ma méthode, celle de Zanna et celle de muru .

Obtenir de la couleur avec find

L'un des avantages immédiats de l'utilisation de globstar est, par défaut sur Ubuntu, de grepproduire une sortie colorisée. Mais vous pouvez facilement obtenir cela findaussi .

Les comptes utilisateurs dans Ubuntu sont créés avec un alias qui fait grepvraiment tourner grep --color=auto(courir alias greppour voir). C'est une bonne chose que les alias ne soient à peu près étendus que lorsque vous les émettez de manière interactive , mais cela signifie que si vous souhaitez findinvoquer grepavec l' --colorindicateur, vous devrez l'écrire explicitement. Par exemple:

find . -name file.txt -exec grep --color=auto -H 'pattern' {} +
Eliah Kagan
la source
Vous voudrez peut-être indiquer plus clairement que vous devez utiliser le bashshell pour que cela fonctionne. Vous le dites implicitement dans "l'option globstar bash shell" mais il peut être facilement manqué par les gens qui lisent trop rapidement.
Stig Hemmer
J'ai supprimé ma réponse car elle a provoqué beaucoup de commentaires critiques. Vous devez donc supprimer la référence à celui-ci dans votre réponse.
sudodus
@StigHemmer Merci - J'ai précisé que tous les shells n'ont pas cette fonctionnalité. Bien que de nombreux shells (pas seulement bash) prennent en charge les **globs traversant le répertoire , votre critique principale est correcte: la présentation de **dans cette réponse est spécifique à bash, avec shopt étant bash uniquement et le terme "globstar" étant (je pense) bash et tcsh uniquement. J'avais passé sous silence cela à l'origine à cause de ces complexités, mais vous avez raison, c'est un peu déroutant. Plutôt que d'en discuter longuement dans cette réponse, j'ai lié à un autre poste (assez complet) qui fait le gros du travail.
Eliah Kagan
@sudodus Je l'ai fait, mais j'espère que c'est temporaire. Moi et d'autres avons trouvé votre réponse précieuse. Il est vrai -eque cela ne devrait pas être appliqué aux chemins, mais cela est facilement corrigé. Pour la première commande, omettez simplement -e. Pour le second, utilisez find . -name file.txt -printf $'\e[32m%p:\e[0m\n' -exec grep -i "pattern" {} \;ou find . -name file.txt -exec printf '\e[32m%s:\e[0m\n' {} \; -exec grep -i "pattern" {} \;. Les utilisateurs préfèrent parfois votre chemin (avec une -eutilisation fixe) aux autres, qui impriment un chemin par ligne correspondante ; le vôtre imprime un chemin par fichier trouvé suivi des greprésultats.
Eliah Kagan
@sudodus Donc, greplui - même ne fera pas ce que vous faites. Certaines autres critiques étaient également erronées. grep -Hrun by -execne colorise pas sans --color(ou GREP_COLOR). IEEE 1003,1 à 2008 ne garantit pas se {}développe dans ##### {}:, mais Ubuntu a trouver GNU, qui fait . Si cela vous convient, je modifierai votre message pour corriger le -ebogue (et clarifier son cas d'utilisation) et vous pourrez voir si vous souhaitez annuler la suppression. (J'ai le représentant pour afficher / modifier les messages supprimés.)
Eliah Kagan
18

Vous n'en avez pas besoin find; greppeut gérer cela parfaitement bien seul:

grep "pattern" . -airn --include="file.txt"

De man grep:

--exclude=GLOB
      Skip  files  whose  base  name  matches  GLOB  (using   wildcard
      matching).   A  file-name  glob  can  use  *,  ?,  and [...]  as
      wildcards, and \ to quote  a  wildcard  or  backslash  character
      literally.

--exclude-from=FILE
      Skip  files  whose  base name matches any of the file-name globs
      read from FILE  (using  wildcard  matching  as  described  under
      --exclude).

--exclude-dir=DIR
      Exclude  directories  matching  the  pattern  DIR from recursive
      searches.

--include=GLOB
      Search  only  files whose base name matches GLOB (using wildcard
      matching as described under --exclude).
muru
la source
Nice - cela semble être la meilleure façon. Simple et efficace. Je souhaite avoir connu (ou pensé à vérifier la page de manuel pour) cette méthode. Merci!
Eliah Kagan du
@EliahKagan Je suis plus surpris que Zanna n'ait pas posté ceci - j'avais montré un exemple de cette option pour une autre réponse il y a quelque temps. :)
muru
2
apprenant lentement, hélas, mais j'y arrive finalement, vos enseignements ne sont pas complètement gaspillés sur moi;)
Zanna
C'est très simple et facile à retenir. Merci.
Rajesh Keladimath
Je suis d'accord que c'est la meilleure réponse. Dois-je retirer ma réponse pour diminuer la confusion, ou la laisser rester pour montrer qu'il existe des alternatives, et ce qui peut être fait avecfind?
sudodus
8

La méthode donnée dans la réponse de muru , de courir grepavec le --includedrapeau pour spécifier un nom de fichier, est souvent le meilleur choix. Cependant, cela peut également être fait avec find.

L'approche de cette réponse utilise findpour s'exécuter grepséparément pour chaque fichier trouvé et imprime le chemin d'accès à chaque fichier exactement une fois , au-dessus des lignes correspondantes trouvées dans chaque fichier. (Les méthodes qui impriment le chemin devant chaque ligne correspondante sont traitées dans d'autres réponses.)


Vous pouvez changer de répertoire en haut de l'arborescence de répertoires où vous avez ces fichiers. Exécutez ensuite:

find . -name "file.txt" -type f -exec echo "##### {}:" \; -exec grep -i "pattern" {} \;

Cela affiche le chemin (par rapport au répertoire actuel ., et y compris le nom du fichier lui-même) de chaque fichier nommé file.txt, suivi de toutes les lignes correspondantes dans le fichier. Cela fonctionne car {}est un espace réservé pour le fichier trouvé. Le chemin de chaque fichier est différent de son contenu en étant préfixé #####et imprimé une seule fois, avant les lignes correspondantes de ce fichier. (Les fichiers appelés file.txtqui ne contiennent aucune correspondance ont toujours leur chemin imprimé.) Vous pouvez trouver cette sortie moins encombrée que ce que vous obtenez des méthodes qui impriment un chemin au début de chaque ligne correspondante.

Une utilisation findcomme celle-ci sera presque toujours plus rapide que l'exécution grepsur chaque fichier ( grep -arin "pattern" *), car findrecherche les fichiers avec le nom correct et ignore tous les autres fichiers.

Ubuntu utilise GNU find , qui se développe toujours {}même lorsqu'il apparaît dans une chaîne plus grande , comme ##### {}:. Si vous avez besoin de votre commande pour travailler findsur des systèmes qui ne le prennent pas en charge , ou si vous préférez utiliser l' -execaction uniquement lorsque cela est absolument nécessaire, vous pouvez utiliser:

find . -name "file.txt" -type f -printf '##### %p:\n' -exec grep -i "pattern" {} \;

Pour faciliter la lecture de la sortie , vous pouvez utiliser des séquences d'échappement ANSI pour obtenir des noms de fichiers colorés. Cela rend l'en-tête du chemin de chaque fichier mieux se démarquer des lignes correspondantes qui s'impriment en dessous:

find . -name file.txt -printf $'\e[32m%p:\e[0m\n' -exec grep -i "pattern" {} \;

Cela oblige votre shell à transformer le code d'échappement pour le vert en la séquence d'échappement réelle qui produit du vert dans un terminal, et à faire la même chose avec le code d'échappement pour la couleur normale. Ces échappements sont passés à find, qui les utilise lors de l'impression d'un nom de fichier. (La $' 'citation est nécessaire ici car findl' -printfaction de ne reconnaît pas \epour interpréter les codes d'échappement ANSI.)

Si vous préférez, vous pouvez utiliser -execà la place la printfcommande du système (qui prend en charge \e). Donc, une autre façon de faire la même chose est:

find . -name file.txt -exec printf '\e[32m%s:\e[0m\n' {} \; -exec grep -i "pattern" {} \;
sudodus
la source
j'allais faire une "boucle for" avec un tableau et je ne pensais pas à l'option native exec de find. Bon! Mais je pense que l'utilisation de dot vous localisera dans le répertoire où vous vous trouvez déjà. Corrigez-moi si je me trompe. Ne serait-il pas préférable de spécifier le directement à analyser dans l'ordre de recherche? find abc/def/efg -name "file.txt" -type f -exec echo -e "##### {}:" \; -exec grep -i "pattern" {} \;
kcdtv
Bien sûr, cela éliminera la commande cd abc/def/efg«changer de répertoire» :-)
sudodus
(1) Pourquoi spécifiez-vous l' -eoption echo? Cela entraînera la modification de tous les noms de fichiers contenant des barres obliques inverses. (2) L'utilisation {}dans le cadre d' un argument n'est pas garantie de fonctionner. Il vaudrait mieux dire -exec echo "#####" {} \;ou -exec printf "##### %s:\n" {} \;. (3) Pourquoi ne pas simplement utiliser -printou -printf? (4) À considérer également grep -H.
G-Man dit `` Réintègre Monica '' le
@ G-man, 1) Parce que j'ai utilisé la couleur ANSI à l'origine: find . -name "file.txt" -type f -exec echo -e "\0033[32m{}:\0033[0m" \; -exec grep -i "pattern" {} \;2) Vous avez peut-être raison, mais jusqu'à présent, cela fonctionne pour moi. 3) -print et -printf sont également des alternatives. 4) C'est déjà là dans la réponse principale. - Quoi qu'il en soit, vous êtes les bienvenus avec votre propre réponse :-)
sudodus
Vous n'avez pas besoin des deux -execappels. Utilisez simplement grep -Het cela imprimera le nom du fichier (en couleur) ainsi que le texte correspondant.
terdon
0

Juste pour signaler que si les conditions de la question peuvent être considérées comme littéraires, vous pouvez utiliser grep direct:

grep 'pattern' abc/def/efg/*/file.txt

ou

grep 'pattern' abc/def/efg/{1..300}/file.txt

la source