web-dev-qa-db-fra.com

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

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 veux rechercher une chaîne uniquement dans ce file.txt à 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 beaucoup de sous-répertoires et de fichiers.

16
Rajesh Keladimath

Dans le répertoire parent, vous pouvez utiliser find puis exécuter grep uniquement sur ces fichiers:

find . -type f -iname "file.txt" -exec grep -Hi "pattern" '{}' +
21
Zanna

Vous pouvez également utiliser globstar.

Construire les commandes grep avec find, comme dans la réponse de Zanna , est un moyen extrêmement robuste, polyvalent et portable de le faire (voir aussi réponse de sudodus ). Et muru a publié une excellente approche consistant à utiliser l'option --include de grep . Mais si vous voulez utiliser uniquement la commande grep et votre Shell, il existe un autre moyen de le faire - vous pouvez créer le Shell lui-même . effectuer la récursion nécessaire :

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

L'indicateur -H fait afficher grep le nom du fichier même si un seul fichier correspondant est trouvé. Vous pouvez également passer les indicateurs -a, -i et -n (de votre exemple) à grep, si c'est ce dont vous avez besoin. Mais ne passez pas -r ou -R lorsque vous utilisez cette méthode. C'est le shell qui rechute dans les répertoires en développant le motif global contenant ** et et non pas grep.

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

Comment ça marche

L'activation de globstar bash option Shell permet à ** de faire correspondre les chemins d'accès contenant le séparateur de répertoire (/). C'est donc un glob récurrent. Plus précisément, comme man bash explique:

Lorsque l'option Shell globstar est activée et que * est utilisé dans un contexte d'expansion de chemin d'accès, deux * adjacents utilisés comme un seul motif correspondent à tous les fichiers et zéro ou plusieurs répertoires et sous-répertoires. Si suivi de /, deux * adjacents ne feront correspondre que des répertoires et des sous-répertoires.

Soyez prudent avec cela, car vous pouvez exécuter des commandes qui modifient ou suppriment beaucoup plus de fichiers que vous le souhaitez, en particulier si vous écrivez ** alors que vous vouliez écrire *. (C'est sûr dans cette commande, qui ne change aucun fichier.) shopt -u globstar désactive l'option Shell globstar.

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

find est beaucoup plus polyvalent que globstar. Tout ce que vous pouvez faire avec globstar, vous pouvez également le faire avec la commande find. 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 cherche pas dans les répertoires dont le nom commence par .. Parfois, vous ne voulez pas recurse 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 sous forme d'arguments à votre commande (grep) à la place du glob lui-même. Si vous avez tellement de fichiers appelés file.txt que 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 fichiers de ce type, mais cela pourrait arriver.

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

  • à la manière de Zanna construit et exécute une commande grep avec potentiellement plusieurs arguments de chemin. Mais si plus de fichiers sont trouvés que ne peuvent être énumérés dans un seul chemin, l'action +- terminée -exec exécute la commande avec certains des chemins, puis l'exécute à nouveau avec d'autres chemins, etc. Dans le cas de greping pour une chaîne dans plusieurs fichiers, cela produit le comportement correct.

    Comme pour la méthode globstar décrite ici, cela affiche toutes les lignes correspondantes, avec des chemins ajoutés à chacune d'elles.

  • façon sudodus exécute grep séparément pour chaque file.txt trouvé. S'il y a beaucoup de fichiers, cela peut être plus lent que d'autres méthodes, mais cela fonctionne.

    Cette méthode trouve les fichiers et imprime leurs chemins, suivis des lignes correspondantes, le cas échéant. C'est un format de sortie différent du format produit par ma méthode, Zanna , et mur .

Obtenir des couleurs avec find

L'un des avantages immédiats de l'utilisation de globstar est que, par défaut sur Ubuntu, grep produira une sortie colorisée. Mais vous pouvez facilement obtenir ceci avec find, aussi .

Les comptes d'utilisateurs dans Ubuntu sont créés avec n alias qui fait que grep exécute vraiment grep --color=auto (exécutez alias grep pour voir). C’est ne bonne chose que les alias sont généralement développés uniquement lorsque vous les publiez de manière interactive , mais cela signifie que si vous souhaitez que find appelle grep avec le drapeau --color, avoir à l'écrire explicitement. Par exemple:

find . -name file.txt -exec grep --color=auto -H 'pattern' {} +
24
Eliah Kagan

Vous n'avez pas besoin de find pour cela; grep peut gérer cela parfaitement tout 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).
18
muru

La méthode indiquée dans réponse de mur , consistant à exécuter grepavec l'indicateur --include pour spécifier un nom de fichier, constitue souvent le meilleur choix. Cependant, ceci peut aussi être fait avec findname __.

L'approche utilisée dans cette réponse utilise findpour exécuter grepséparément pour chaque fichier trouvé et imprime le chemin de chaque fichier exactement une fois , au-dessus des lignes correspondantes trouvées dans chaque fichier. (Les méthodes imprimant 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 dans laquelle vous avez ces fichiers. Puis lancez:

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

Cela imprime le chemin (par rapport au répertoire en cours, . et le nom du fichier lui-même) de chaque fichier nommé file.txt, suivi de toutes les lignes correspondantes du fichier. Cela fonctionne car {} est un espace réservé pour le fichier trouvé. Le chemin de chaque fichier est séparé de son contenu par le préfixe ##### et n'est imprimé qu'une seule fois, avant les lignes correspondantes de ce fichier. (Les chemins nommés file.txt qui ne contiennent aucune correspondance ont toujours leur chemin imprimé.) Vous risquez de trouver cette sortie moins encombrée que ce que vous obtenez avec les méthodes qui affichent un chemin au début de chaque ligne correspondante.

Utiliser findcomme ceci sera presque toujours plus rapide que d'exécuter grepsur tous les fichiers (grep -arin "pattern" *), car findrecherche les fichiers avec le nom correct et ignore tous les autres fichiers.

buntu utilise GNU find , qui étend toujours {} même lorsqu'il apparaît dans une chaîne plus grande , comme ##### {}:. Si vous avez besoin de votre commande pour travailler avec findsur des systèmes qui pourraient ne pas supporter cela , ou si vous préférez utiliser l'action -exec uniquement en cas d'absolue nécessité, 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 le titre de chaque fichier plus visible par rapport aux lignes correspondantes imprimées sous celui-ci:

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

Cela provoque votre Shell à tourner le code d'échappement pour le vert dans la séquence d'échappement réelle qui produit le vert dans un terminal, et faire la même chose avec le code d'échappement pour la couleur normale . Ces échappements sont passés à findname__, qui les utilise pour imprimer un nom de fichier. (La citation de $'' est nécessaire ici car l’action -printf de findname __ ne reconnaît pas \e pour l’interprétation des codes d’échappement ANSI.)

Si vous préférez, vous pouvez utiliser plutôt -exec avec commande printfdu 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" {} \;
8
sudodus

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

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

ou

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