Comment puis-je effectuer xargs grep sur une sortie grep qui a des espaces?

8

Je recherche des fichiers en fonction d'une expression régulière, puis j'essaie de rechercher du contenu dans ces fichiers. Ainsi, par exemple, j'ai quelque chose comme

#Find all C++ files that match a certain pattern and then search them
find . -name "*.cpp" | grep "<name regex>" | xargs grep "<content regex>"

Le problème que je rencontre est que certains des chemins contiennent des espaces, ce qui est déroutant xargs. Je sais que si j'utilisais simplement find, je pourrais utiliser l' -print0argument (avec l' -0argument xargsactivé) pour empêcher les xargs de traiter les espaces comme des délimiteurs. Y a-t-il quelque chose de similaire avec grep?

Ou est-ce que j'aborde complètement ce problème de la mauvaise façon? Naïvement, findto a grepdu xargs grepsens pour moi, mais je suis ouvert à d'autres approches qui donnent les mêmes résultats.

quantique
la source
2
vous pouvez positionner les arguments avec xargsen utilisant le -iparamètre, a la cat sample.txt | grep "pat t ern" | xargs -i grep "{}"- les accolades indiquent où positionner l'argument. Le manuel me dit que -ic'est déconseillé en faveur de -Idonc peut-être la peine d'y jeter un œil.
dougBTV

Réponses:

5

Utilisez peut-être quelque chose comme ça (si gnu grep).

grep -r 'content pattern' --include==*.cpp

homme grep

--include = GLOB Recherche uniquement les fichiers dont le nom de base correspond à GLOB (en utilisant la correspondance générique comme décrit sous --exclude)

Voir également les options pour les délimiteurs nuls.

-Z, --null Affiche un octet zéro (le caractère ASCII NUL) au lieu du caractère qui suit normalement un nom de fichier. Par exemple, grep -lZ affiche un octet zéro après chaque nom de fichier au lieu de la nouvelle ligne habituelle. Cette option rend la sortie sans ambiguïté, même en présence de noms de fichiers contenant des caractères inhabituels comme des retours à la ligne. Cette option peut être utilisée avec des commandes telles que find -print0, perl -0, sort -z et xargs -0 pour traiter les noms de fichiers arbitraires, même ceux qui contiennent des caractères de nouvelle ligne.

-z, --null-data Traite l'entrée comme un ensemble de lignes, chacune se terminant par un octet zéro (le caractère ASCII NUL) au lieu d'une nouvelle ligne. Comme l'option -Z ou --null, cette option peut être utilisée avec des commandes comme sort -z pour traiter des noms de fichiers arbitraires.

Zoredache
la source
Notez qu'il grep -r include='*.cpp's'agit d'un glob de coquille - et il en est de même des fonctionnalités alignées w / find . -name '*.cpp' -exec grep -e 'content_pattern' -- {} \;not w /find . -name '*.cpp' | grep 'name_pattern' | xargs grep 'content_pattern'
mikeserv
4

Si vous devez sauter à travers de nombreux cerceaux, alors l'efficacité des xargs est de toute façon perdue. Voici un travail grossier:

find . -iname "*.cpp" | grep "<pattern>" | while read -r x; do grep exa "$x"; done

Chaque fois que je rencontre des problèmes avec des espaces dans les noms de fichiers, la réponse est des guillemets doubles sur une variable.

Baazigar
la source
Cela exécute le grep interne de la boucle uniquement pour chaque ligne trouvée par le grep externe. C'est beaucoup de frais généraux.
Adam Katz
3

Utilisez findpour effectuer tout le filtrage des noms de fichiers. Plutôt que

find . -name "*.cpp" | grep "foo" | xargs grep 

faire

find . -name "*.cpp" -name "*foo*" -print0 | xargs -0 grep 

Si vous voulez faire quelque chose de légèrement plus compliqué, comme

find . -name "*.cpp" | egrep "foo|bar" | xargs grep 

tu peux faire

find . -name "*.cpp" "(" -name "*foo*" -o -name "*bar*" ")" -print0 | xargs -0 grep 

Notez que ceux-ci devraient fonctionner même pour les fichiers avec des retours à la ligne dans leurs noms.

Et, si vous avez besoin de la puissance d'expressions régulières à part entière, vous pouvez utiliser -regex.

Scott
la source
2

Cela devrait fonctionner même sans outils GNU:

#Find all C++ files that match a certain pattern and then search them
find . -name "*.cpp"  | grep "<name regex>" | perl -pe 's/\n/\0/' \
  | xargs -0 grep "<content regex>"

L' perlappel remplace les sauts de ligne par des caractères nuls, ce qui permettra xargs -0d'interpréter les entrées ligne par ligne plutôt que par espace blanc.

En utilisant GNU, vous pouvez supprimer l' perlappel et passer xargs -0 …àxargs -d "\n" …

Vous n'avez pas perlou GNU? Essayez awk '{printf "%s%c", $0, 0}'plutôt.

Adam Katz
la source
1
Cela peut ne pas faire la bonne chose si certains des noms de fichiers incluent des retours à la ligne (une occurrence plutôt inhabituelle, bien sûr, mais pas impossible).
dhag
@dhag a un point valable concernant xargs -d "\n". C'est un événement très inhabituel, mais si vous n'avez pas le contrôle des données et que vous craignez que ce soit un risque pour la sécurité, faites attention aux attentes de sortie.
Adam Katz