Quand est-ce que xargs est nécessaire?

134

La xargscommande me confond toujours. Y at-il une règle générale pour cela?

Considérez les deux exemples ci-dessous:

$ \ls | grep Cases | less

imprime les fichiers qui correspondent à 'Cas', mais changer la commande en touchnécessite xargs:

$ \ls | grep Cases | touch
touch: missing file operand
Try `touch --help' for more information.

$ \ls | grep Cases | xargs touch
Zaid
la source

Réponses:

143

La différence réside dans les données acceptées par le programme cible.

Si vous utilisez uniquement un canal, il reçoit des données sur STDIN (le flux d'entrée standard) sous forme de pile de données brutes qu'il peut trier sur une ligne à la fois. Cependant, certains programmes n'acceptent pas leurs commandes en standard, ils s'attendent à ce que cela soit précisé dans les arguments de la commande. Par exemple touchprend un nom de fichier en tant que paramètre sur la ligne de commande comme ceci: touch file1.txt.

Si vous avez un programme qui génère des noms de fichiers sur la sortie standard et que vous voulez les utiliser comme arguments pour touch, vous devez utiliser xargsqui lit les données de flux stdin et convertit chaque ligne dans l' espace arguments séparés à la commande.

Ces deux choses sont équivalentes:

# touch file1.txt
# echo file1.txt | xargs touch

Ne pas utiliser xargssauf si vous savez exactement ce que vous faites et pourquoi vous en avez besoin. Il est souvent fréquent qu'il existe un meilleur moyen de faire le travail que xargsde forcer la conversion. Le processus de conversion est également semé d'embûches potentielles telles que la fuite, l'expansion des mots, etc.

Caleb
la source
2
L'avertissement me sent un peu ficelle. Parmi les deux options courantes pour obtenir un flux sur une ligne de commande ( xargset $(...)), xargs est beaucoup plus sûr que la substitution de commande. Et je ne me souviens pas avoir jamais rencontré un nom de fichier légitime contenant une nouvelle ligne. Les problèmes liés aux échappements et à l’expansion des mots liés à la substitution de commande ne sont-ils pas xargs?
camh
6
@camh: Ce sont des pièges potentiels avec les deux. Dans le shell, vous devez vous soucier de la division des noms de fichiers en espaces, tabulations et nouvelles lignes. Dans xargs, vous n'avez plus qu'à vous soucier des nouvelles lignes. Dans xargs, si votre sortie est correctement formatée, vous pouvez diviser des mots / noms de fichiers sur le caractère NUL à la place ( xargs -0), ce qui est utile avec find -print0.
Ken Bloom
Appelle xargs- t -il le programme via le shell avec des arguments séparés par des espaces, ou construit-il réellement la liste des arguments en interne (par exemple pour une utilisation avec execv/ execp)?
Détly
1
Il le construit en interne et utilise execvp, donc c'est sûr. En outre, xargs GNU (utilisé sous Linux et quelques autres) vous permet de spécifier newline comme délimiteur -d \n, bien que les xargs BSD (OSX et autres) ne semblent pas prendre en charge cette option.
moelleux
72

Pour compléter les réponses déjà fournies, vous xargspouvez réaliser une chose intéressante qui devient de plus en plus importante dans le paysage informatique multicœur et distribué de nos jours: il est possible de traiter en parallèle des travaux.

Par exemple:

$ find . -type f -name '*.wav' -print0 |xargs -0 -P 3 -n 1 flac -V8

va encoder * .wav => * .flac, en utilisant trois processus à la fois ( -P 3).

amphétamachine
la source
Sensationnel. J'aurais dû le savoir il y a une semaine lorsque je faisais exactement la même chose (à l'exception de l'utilisation d'OGG) avec 50 Go de WAV. :)
Alois Mahdal
pourquoi ne pas utiliser le paramètre -exec que find a?
Evgeny
3
@Evgeny Le -execparamètre ne traitera pas les tâches en parallèle.
amphetamachine
Il est bon de noter que l' -0argument pour lexargs faire considère que le NULLcaractère est le délimiteur d'élément d'entrée. find -print0sortie des éléments délimités par NULL. C'est une bonne pratique pour les noms de fichiers pouvant contenir des espaces, des guillemets ou d'autres caractères spéciaux.
Dan Dascalescu le
24

xargs est particulièrement utile lorsque vous avez une liste de chemins de fichiers sur stdin et que vous voulez faire quelque chose avec eux. Par exemple:

$ git ls-files "*.tex" | xargs -n 1 sed -i "s/color/colour/g"

Examinons cette étape par étape:

$ git ls-files "*.tex"
tex/ch1/intro.tex
tex/ch1/motivation.tex
....

En d’autres termes, notre entrée est une liste de chemins vers lesquels nous voulons faire quelque chose.

Pour savoir ce que fait xargs avec ces chemins, une bonne astuce consiste à ajouter echoavant votre commande, comme suit:

$ git ls-files "*.tex" | xargs -n 1 echo sed -i "s/color/colour/g"
sed -i "s/color/colour/g" tex/ch1/intro.tex
sed -i "s/color/colour/g" tex/ch1/motivation.tex
....

L' -n 1argument fera en sorte que xargs transforme chaque ligne en une commande qui lui est propre. La sed -i "s/color/colour/g"commande remplacera toutes les occurrences de colorwith colourpour le fichier spécifié.

Notez que cela ne fonctionne que s'il n'y a pas d'espaces dans vos chemins. Si vous le faites, vous devez utiliser des chemins nuls comme terminaison comme entrée de xargs en transmettant l' -0indicateur. Un exemple d'utilisation serait:

$ git ls-files -z "*.tex" | xargs -0 -n 1 sed -i "s/color/colour/g"

Ce qui fait la même chose que ce que nous avons décrit ci-dessus, mais fonctionne également si l'un des chemins contient un espace.

Cela fonctionne avec n'importe quelle commande produisant des noms de fichiers en sortie tels que findou locate. Si vous l'utilisez dans un dépôt git avec beaucoup de fichiers, il peut être plus efficace de l'utiliser avec git grep -lau lieu de git ls-files, comme ceci:

$ git grep -l "color" "*.tex" | xargs -n 1 sed -i "s/color/colour/g"

La git grep -l "color" "*.tex"commande donnera une liste de fichiers "* .tex" contenant la phrase "couleur".

Sverre Rabbelier
la source
1
C'est vrai, mais si vous avez appris cela, vous devez également savoir pourquoi la création d'une boucle sur la sortie de find est-elle une mauvaise pratique?
Wildcard
6

Votre premier argument illustre assez bien la différence.

\ls | grep Cases | lessvous permet de parcourir la liste des noms de fichiers produits par lset grep. Peu importe qu’il s’agisse de noms de fichiers, c’est juste du texte.

\ls | grep Cases | xargs lessvous permet de parcourir les fichiers dont les noms sont générés par la première partie de la commande. xargsprend une liste de noms de fichiers en entrée et une commande sur sa ligne de commande et exécute la commande avec les noms de fichiers sur sa ligne de commande.

Lors de l' examen à l' aide xargs, garder à l' esprit qu'il attend une entrée en forme d'une manière étrange: délimité par des espaces, avec \, 'et "utilisé pour citer (d'une manière inhabituelle, parce que \n'est pas des citations spéciales à l' intérieur). Utilisez uniquement xargssi vos noms de fichiers ne contiennent pas d’espace ni de \'".

Gilles
la source
@Gilles: xargs a l' -0, --nulloption de contourner le problème des espaces (c'est fort probable que je l'ai appris de vous :), alors je suppose que vous parlez d'un xargappel sans options , mais je suis perplexe devant votre référence aux citations. Avez-vous un lien ou un exemple à ce sujet? .. (ps. | xargs lessest un "truc" très pratique +1 .. merci ..
Peter.O
4

Dans votre exemple, vous n'avez pas besoin de l'utiliser xargscar vous findferez exactement et en toute sécurité ce que vous voulez faire.

Exactement ce que vous voulez utiliser, findc'est:

find -maxdepth 1 -name '*Cases*' -exec touch {} +

Dans cet exemple, cela -maxdepth 1signifie que vous recherchez uniquement dans le répertoire en cours, ne descendez dans aucun sous-répertoire; Par défaut, find cherchera dans tous les sous-répertoires (ce qui est souvent ce que vous voulez), sauf si vous le contraignez avec maxdepth. Le {}est le nom du fichier qui sera remplacé à la place et le +est l'un des deux marqueurs de fin de commande, l'autre étant ;. La différence entre eux est que ;signifie exécuter la commande sur chaque fichier un à la fois, alors que +signifie exécuter la commande sur tous les fichiers à la fois. Notez cependant que votre shell va probablement essayer de s’interpréter ;lui-même, vous devrez donc y échapper avec \;ou ';'. Oui, finda un certain nombre de petits ennuis comme celui-ci, mais sa puissance compense largement.

Les deux findet xargssont difficiles à apprendre au début. Pour vous aider à apprendre, xargsessayez d’utiliser l’ option -pou --interactivequi vous montrera la commande qu’elle est sur le point d’exécuter et vous demande si vous souhaitez ou non l’exécuter.

De même, findvous pouvez utiliser -okà la place de -execpour vous demander si vous souhaitez ou non exécuter la commande.

Il findarrive cependant que, dans certains cas, vous ne puissiez pas faire tout ce que vous voulez et que ce soit le cas xargs. La -execcommande n'acceptera qu'une seule occurrence d' {}apparition, donc si vous obtenez une erreur find -type f -exec cp {} {}.bak \;, vous pouvez le faire comme suit. :find -type f -print0 | xargs -0 -l1 -IX cp X X.bak

Vous pouvez en apprendre plus sur les commandes d’ exécution dans le manuel GNU Findutils .

De plus, j'ai mentionné que findvous faites ce que vous voulez en toute sécurité, car lorsque vous traitez avec des fichiers, vous allez rencontrer des espaces et d'autres caractères qui poseront problème, xargssauf si vous utilisez l' option -0ou --nullavec quelque chose qui génère des éléments de saisie terminés par un caractère nul. des espaces.

aculich
la source
Les noms de fichiers @Wildcard avec des espaces ou des caractères tels que 'ou "peuvent être problématiques, alors que findces cas seront traités sans problème.
Aculich
Oui je sais. Voir ma réponse à la question liée . J'aurais probablement dû reformuler cette question pour en faire une déclaration dans le commentaire ci-dessus, ou ajouter la phrase "Voir la question ..." devant elle. : D
Wildcard
1

xargs(avec find, sort, du, uniq, perlet quelques autres) accepte un commutateur de ligne de commande pour dire « STDIN a une liste de fichiers, séparés par un octet NUL (0x00) ». Cela facilite la gestion des noms de fichiers contenant des espaces et d’autres personnages amusants. Les noms de fichiers ne contiennent pas de NUL.

Waltinator
la source
2
Je pense que vous voulez dire "les noms de fichiers ne peuvent pas contenir de zéros".
Amphetamachine