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.txt
fichier à 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.
command-line
grep
find
Rajesh Keladimath
la source
la source
Réponses:
Dans le répertoire parent, vous pouvez utiliser
find
puis exécutergrep
uniquement sur ces fichiers:la source
-H
àgrep
afin 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).Vous pouvez également utiliser globstar.
Construire des
grep
commandes avecfind
, 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'utilisationgrep
de l'--include
option de . Mais si vous souhaitez utiliser uniquement lagrep
commande 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 :L'
-H
indicateur faitgrep
afficher le nom du fichier même si un seul fichier correspondant est trouvé. Vous pouvez passer la-a
,-i
et des-n
drapeaux (de votre exemple) àgrep
aussi bien, 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 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 bash
explique: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 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 aussi le faire avec lafind
commande. 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ésfile.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 ces fichiers, mais cela pourrait arriver.Les méthodes qui utilisent
find
ne sont pas soumises à cette restriction, car:La façon dont Zanna construit et exécute une
grep
commande 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-exec
exécute la commande avec certains des chemins, puis l'exécute à nouveau avec quelques chemins supplémentaires, etc. Dans le cas d'grep
ing 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
grep
séparément pour chaquefile.txt
trouvé. 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
grep
produire une sortie colorisée. Mais vous pouvez facilement obtenir celafind
aussi .Les comptes utilisateurs dans Ubuntu sont créés avec un alias qui fait
grep
vraiment tournergrep --color=auto
(couriralias grep
pour 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 souhaitezfind
invoquergrep
avec l'--color
indicateur, vous devrez l'écrire explicitement. Par exemple:la source
bash
shell 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.**
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.-e
que cela ne devrait pas être appliqué aux chemins, mais cela est facilement corrigé. Pour la première commande, omettez simplement-e
. Pour le second, utilisezfind . -name file.txt -printf $'\e[32m%p:\e[0m\n' -exec grep -i "pattern" {} \;
oufind . -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-e
utilisation fixe) aux autres, qui impriment un chemin par ligne correspondante ; le vôtre imprime un chemin par fichier trouvé suivi desgrep
résultats.grep
lui - même ne fera pas ce que vous faites. Certaines autres critiques étaient également erronées.grep -H
run by-exec
ne colorise pas sans--color
(ouGREP_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-e
bogue (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.)Vous n'en avez pas besoin
find
;grep
peut gérer cela parfaitement bien seul:De
man grep
:la source
find?
La méthode donnée dans la réponse de muru , de courir
grep
avec le--include
drapeau pour spécifier un nom de fichier, est souvent le meilleur choix. Cependant, cela peut également être fait avecfind
.L'approche de cette réponse utilise
find
pour s'exécutergrep
sé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:
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ésfile.txt
qui 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
find
comme celle-ci sera presque toujours plus rapide que l'exécutiongrep
sur chaque fichier (grep -arin "pattern" *
), carfind
recherche 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 travaillerfind
sur des systèmes qui ne le prennent pas en charge , ou si vous préférez utiliser l'-exec
action uniquement lorsque cela est absolument nécessaire, vous pouvez utiliser: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:
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 carfind
l'-printf
action de ne reconnaît pas\e
pour interpréter les codes d'échappement ANSI.)Si vous préférez, vous pouvez utiliser
-exec
à la place laprintf
commande du système (qui prend en charge\e
). Donc, une autre façon de faire la même chose est:la source
find abc/def/efg -name "file.txt" -type f -exec echo -e "##### {}:" \; -exec grep -i "pattern" {} \;
cd abc/def/efg
«changer de répertoire» :-)-e
optionecho
? 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-print
ou-printf
? (4) À considérer égalementgrep -H
.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 :-)-exec
appels. Utilisez simplementgrep -H
et cela imprimera le nom du fichier (en couleur) ainsi que le texte correspondant.Juste pour signaler que si les conditions de la question peuvent être considérées comme littéraires, vous pouvez utiliser grep direct:
ou
la source