Que fait «xargs grep»?

25

Je connais la grepcommande et j'apprends les fonctionnalités de xargs, alors j'ai lu cette page qui donne quelques exemples sur la façon d'utiliser la xargscommande.

Je suis confus par le dernier exemple, l'exemple 10. Il dit "La commande xargs exécute la commande grep pour trouver tous les fichiers (parmi les fichiers fournis par la commande find) qui contenaient une chaîne 'stdlib.h'"

$ find . -name '*.c' | xargs grep 'stdlib.h'
./tgsthreads.c:#include
./valgrind.c:#include
./direntry.c:#include
./xvirus.c:#include
./temp.c:#include
...
...
...

Cependant, quelle différence y a-t-il à utiliser simplement

$ find . -name '*.c' | grep 'stdlib.h'

?

Évidemment, je lutte toujours avec ce que fait exactement xargs, donc toute aide est appréciée!

AlphaOmega
la source
2
Cette question peut être utile: quand faut-il utiliser XArgs?
TheOdd

Réponses:

30
$ find . -name '*.c' | grep 'stdlib.h'

Cela dirige la sortie (stdout) * de find(stdin de) * grep 'stdlib.h' sous forme de texte (c'est-à-dire que les noms de fichiers sont traités comme du texte). grepfait son travail habituel et trouve les lignes correspondantes dans ce texte (tous les noms de fichiers qui contiennent eux-mêmes le modèle). Le contenu des fichiers n'est jamais lu.

$ find . -name '*.c' | xargs grep 'stdlib.h'

Ceci construit une commande grep 'stdlib.h' dont chaque résultat findest un argument - donc cela cherchera des correspondances à l' intérieur de chaque fichier trouvé par find( xargspeut être considéré comme transformant son stdin en arguments aux commandes données) *

Utilisez-le -type fdans votre commande find, sinon vous obtiendrez des erreurs greppour les répertoires correspondants. De plus, si les noms de fichiers ont des espaces, ils xargsvont mal se visser, alors utilisez le séparateur nul en ajoutant -print0et xargs -0pour des résultats plus fiables:

find . -type f -name '*.c' -print0 | xargs -0 grep 'stdlib.h'

* ajouté ces points explicatifs supplémentaires comme suggéré dans le commentaire de @cat

Zanna
la source
2
vous pourriez envisager de noter (car il semble que vous l'ayez omis) le point clé qui |redirige stdout vers stdin de grep qui n'est pas le même que les arguments de grep et donne des résultats confus.
chat
1
Ou utilisez les trouvailles GNU find -name '*.c' -exec grep stdlib.h {} +. Je n'utilise pratiquement pas de xargs. Aussi surpris que personne n'ait mentionné que xargs sert un objectif similaire à la grep $(find)substitution de commandes, j'ai donc écrit ma propre réponse. Expliquer xargs comme substitution de commande avec moins de limitations et de problèmes semble naturel.
Peter Cordes
Une situation sur laquelle j'utilise xargs est si je supprime beaucoup de fichiers à la suite de la recherche. Si vous faites juste -exec rm, il exécutera rm sur chaque fichier un par un, ce qui est très inefficace. Le piping vers xargs les fera tous en même temps avec un rm. Limiter avec say -n50 (faire 50 à la fois) pourrait empêcher le débordement de la ligne de commande (problème avec beaucoup de fichiers).
lsd
1
@lsd: Pourquoi pas find -deletepour ce cas particulier? Ou pour les commandes autres que rm, si vous avez GNU find, puis les -exec some_command {} +groupes en lots comme xargs, au lieu du \;comportement d'exécution de la commande séparément pour chacun.
Peter Cordes
@lsd findexécute la commande sur chaque fichier si et seulement s'il utilise -exec command \;Both xargset -exec command \+appellera la commande avec le nombre maximum d'arguments autorisés par le système. En d'autres termes, ils sont équivalents
Sergiy Kolodyazhnyy
6

xargs prend son entrée standard et la transforme en arguments de ligne de commande.

find . -name '*.c' | xargs grep 'stdlib.h' est très similaire à

grep 'stdlib.h' $(find . -name '*.c')  # UNSAFE, DON'T USE

Et donnera les mêmes résultats tant que la liste des noms de fichiers n'est pas trop longue pour une seule ligne de commande. (Linux prend en charge des mégaoctets de texte sur une seule ligne de commande, donc vous n'avez généralement pas besoin de xargs.)


Mais ces deux choses sont nulles, car elles se cassent si vos noms de fichiers contiennent des espaces . Au lieu de cela, find -print0 | xargs -0fonctionne, mais il en va de même

find . -name '*.c' -exec grep 'stdlib.h' {} +

Cela ne finddirige jamais les noms de fichiers n'importe où: les regroupe dans une grande ligne de commande et s'exécutegrep directement.

\;au lieu d' +exécuter grep séparément pour chaque fichier, ce qui est beaucoup plus lent. Ne fais pas ça. Mais +c'est une extension GNU, vous devez xargsdonc le faire efficacement si vous ne pouvez pas supposer que GNU trouve.


Si vous omettez xargs, find | grepson modèle correspond-il à la liste des noms de fichiersfind s'imprime.

Donc, à ce stade, vous pourriez tout aussi bien faire find -name stdlib.h . Bien sûr, avec -name '*.c' -name stdlib.h, vous n'obtiendrez aucune sortie car ces modèles ne peuvent pas correspondre, et le comportement par défaut de find est de ET les règles ensemble.

Remplacer less à tout moment du processus pour voir quelle sortie n'importe quelle partie du pipeline produit.


Pour en savoir plus: http://mywiki.wooledge.org/BashFAQ a quelques bonnes choses.

Peter Cordes
la source
1
GNU xargs doit également -ddéfinir le séparateur, de sorte que vous pouvez utiliser -d'\n'pour gérer une liste séparée par des retours à la ligne, ce qui peut être utile si vous gérez une liste de noms de fichiers dans un fichier, etc. (tant que les noms de fichiers n'ont pas de nouvelles lignes en eux, c'est.)
ilkkachu
@ilkkachu: oui, les sauts de ligne dans les noms de fichiers sont beaucoup plus rares que les espaces, car ils cassent la plupart des scripts. myfunc(){ local IFS=$'\n'; fgrep stdlib.h` $ (find) ; }fonctionne également avec le même effet. Ou en tant que ligne unique, un (IFS=...; cmd...)sous - shell fonctionne également pour contenir le changement d'IFS sans avoir à l'enregistrer / le restaurer.
Peter Cordes
@PeterCordes Veuillez ne pas faire de command $( find )type de choses. Les noms de fichiers problématiques avec des espaces et des caractères spéciaux peuvent briser ce type de chose. Au moins, citez deux fois la substitution de commande.
Sergiy Kolodyazhnyy
@SergiyKolodyazhnyy: Merci d'avoir souligné qu'il semble que je recommande réellement de le faire. Les personnes qui ont survolé peuvent avoir copié / collé cela au lieu de lire la section suivante. Mis à jour pour y remédier.
Peter Cordes
@SergiyKolodyazhnyy: Ou répondiez-vous à mon commentaire? Remarquez que j'ai défini IFSce qui équivaut à utiliser xargs '-d\n'. L'expansion des globes et le traitement des métacaractères du shell se produisent avant les effets de la substitution de commandes, donc je pense que c'est sûr même avec les noms de fichiers qui contiennent $()ou >. A convenu que l'utilisation de la séparation des mots sur la substitution de commandes n'est pas une bonne pratique, sauf pour une utilisation interactive unique où vous savez quelque chose sur les noms de fichiers. Mais command "$(find)"n'est utile que si vous vous attendez à ce qu'il produise exactement 1 nom de fichier ...
Peter Cordes
5

En général, xargsest utilisé dans les cas où vous dirigeriez (avec le symbole |) quelque chose d'une commande à l'autre ( Command1 | Command2), mais la sortie de la première commande n'est pas correctement reçue comme entrée pour la deuxième commande.

Cela se produit généralement lorsque la deuxième commande ne gère pas correctement l'entrée de données via Standard In (stdin) (par exemple: plusieurs lignes en entrée, la façon dont les lignes sont configurées, les caractères utilisés en entrée, plusieurs paramètres en entrée, le type de données reçu en tant que entrée, etc.). Pour vous donner un exemple rapide, testez les éléments suivants:

Exemple 1:

ls | echo- Cela ne fera rien car echone sait pas gérer l'entrée qu'il reçoit. Maintenant, dans ce cas, si nous l'utilisons, xargsil traitera l'entrée d'une manière qui peut être gérée correctement par echo( par exemple: comme une seule ligne d'information)

ls | xargs echo- Cela affichera toutes les informations lssur une seule ligne

Exemple 2:

Disons que j'ai plusieurs fichiers goLang dans un dossier appelé go. Je les chercherais avec quelque chose comme ça:

find go -name *.go -type f | echo- Mais si le symbole du tuyau là-bas et echoà la fin, ça ne marcherait pas.

find go -name *.go -type f | xargs echo- Ici, cela fonctionnerait grâce à xargsmais si je voulais chaque réponse de la findcommande sur une seule ligne, je ferais ce qui suit:

find go -name *.go -type f | xargs -0 echo- Dans ce cas, la même sortie de findserait indiquée par echo.

Les commandes comme cp, echo, rm, lesset celles qui ont besoin d'une meilleure façon de gérer l'entrée obtiennent un avantage lorsqu'elles sont utilisées avec xargs.

Luis Alvarado
la source
4

xargs est utilisé pour générer automatiquement des arguments de ligne de commande basés (généralement) sur une liste de fichiers.

Considérant donc quelques alternatives à l'utilisation de la xargscommande followoing :

find . -name '*.c' -print0 | xargs -0 grep 'stdlib.h'

Il y a plusieurs raisons de l'utiliser au lieu d'autres options qui n'étaient pas mentionnées à l'origine dans d'autres réponses:

  1. find . -name '*.c' -exec grep 'stdlib.h' {}\;générera un grepprocessus pour chaque fichier - ceci est généralement considéré comme une mauvaise pratique et peut mettre une grosse charge sur le système s'il y a plusieurs fichiers trouvés.
  2. S'il y a beaucoup de fichiers, une grep 'stdlib.h' $(find . -name '*.c')commande échouera probablement, car la sortie de l' $(...)opération dépassera la longueur de ligne de commande maximale du shell

Comme mentionné dans d'autres réponses, la raison d'utiliser l' -print0argument to finddans ce scénario et l' -0argument xargs est que les noms de fichiers avec certains caractères (par exemple les guillemets, les espaces ou même les nouvelles lignes) sont toujours traités correctement.

Michael Firth
la source