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.
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" '{}' +
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 .
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.
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 grep
ing 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 .
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' {} +
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).
La méthode indiquée dans réponse de mur , consistant à exécuter grep
avec l'indicateur --include
pour spécifier un nom de fichier, constitue souvent le meilleur choix. Cependant, ceci peut aussi être fait avec find
name __.
L'approche utilisée dans cette réponse utilise find
pour exécuter grep
sé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 find
comme ceci sera presque toujours plus rapide que d'exécuter grep
sur tous les fichiers (grep -arin "pattern" *
), car find
recherche 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 find
sur 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 à find
name__, qui les utilise pour imprimer un nom de fichier. (La citation de $'
'
est nécessaire ici car l’action -printf
de find
name __ 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 printf
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" {} \;
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