439 votes

Faire en sorte que xargs exécute la commande une fois pour chaque ligne d'entrée

Comment puis-je faire en sorte que xargs exécute la commande exactement une fois pour chaque ligne d'entrée donnée ? Le comportement par défaut de xargs est de découper les lignes et d'exécuter la commande une fois, en passant plusieurs lignes à chaque instance.

Desde http://en.wikipedia.org/wiki/Xargs :

find /path -type f -print0 | xargs -0 rm

Dans cet exemple, find alimente l'entrée de xargs avec une longue liste de noms de fichiers. xargs divise ensuite cette liste en sous-listes et appelle rm une fois pour chaque sous-liste. Ceci est plus efficace que cette version fonctionnellement équivalente :

find /path -type f -exec rm '{}' \ ;

Je sais que find a le drapeau "exec". Je ne fais que citer un exemple illustratif tiré d'une autre ressource.

5 votes

Dans l'exemple que vous donnez, find /path -type f -delete serait encore plus efficace :)

0 votes

Essayez de ne pas utiliser xargs...

7 votes

OP, je sais que cette question est très ancienne, mais elle apparaît toujours sur Google et, à mon avis, la réponse acceptée est fausse. Voir ma réponse plus longue ci-dessous.

473voto

Draemon Points 15448

Ce qui suit ne fonctionnera que si vous n'avez pas d'espaces dans votre saisie :

xargs -L 1
xargs --max-lines=1 # synonym for the -L option

de la page de manuel :

-L max-lines
          Use at most max-lines nonblank input lines per command line.
          Trailing blanks cause an input line to be logically continued  on
          the next input line.  Implies -x.

14 votes

Pour moi, cela peut se traduire par xargs -n 1 comme celui que vous avez donné montre "liste d'arguments trop longue".

24 votes

Si MAX-LINES est omis, la valeur par défaut est 1, donc xargs -l est suffisant. Voir info xargs .

0 votes

Egalement utile pour restaurer tous les fichiers supprimés en utilisant git. git ls-files --deleted | xargs -L 1 git checkout --

297voto

Tobia Points 2978

Il me semble que toutes les réponses existantes sur cette page sont fausses, y compris celle qui est marquée comme correcte. Cela vient du fait que la question est formulée de manière ambiguë.

Résumé :   Si vous voulez exécuter la commande "exactement une fois pour chaque ligne d'entrée," en passant la ligne entière (sans nouvelle ligne) à la commande comme un un seul argument, alors c'est la meilleure façon de le faire, compatible avec UNIX :

... | tr '\n' '\0' | xargs -0 -n1 ...

Si vous utilisez GNU xargs et n'ont pas besoin d'être compatibles avec tous les autres UNIX (FreeBSD, Mac OS X, etc.), vous pouvez alors utiliser l'option spécifique à GNU -d :

... | xargs -d\\n -n1 ...

Maintenant pour la longue explication


Il y a deux problèmes à prendre en compte lors de l'utilisation de xargs :

  1. comment il divise l'entrée en "arguments" ; et
  2. le nombre d'arguments à passer à la commande enfant à la fois.

Pour tester le comportement de xargs, nous avons besoin d'un utilitaire qui montre combien de fois il est exécuté et avec combien d'arguments. Je ne sais pas s'il existe un utilitaire standard pour faire cela, mais nous pouvons le coder assez facilement en bash :

#!/bin/bash
echo -n "-> "; for a in "$@"; do echo -n "\"$a\" "; done; echo

En supposant que vous l'ayez enregistré sous show dans votre répertoire courant et le rendre exécutable, voici comment cela fonctionne :

$ ./show one two 'three and four'
-> "one" "two" "three and four" 

Maintenant, si la question initiale porte réellement sur le point 2. ci-dessus (comme je le pense, après l'avoir relu plusieurs fois) et qu'elle doit être lue comme suit (les changements sont en gras) :

Comment puis-je faire en sorte que xargs exécute la commande exactement une fois pour chaque argument de l'apport donné ? Son comportement par défaut est de fragmenter le entrée dans les arguments et exécutez la commande le moins de fois possible en passant par plusieurs arguments à chaque instance.

alors la réponse est -n 1 .

Comparons le comportement par défaut de xargs, qui divise l'entrée autour des espaces blancs et appelle la commande aussi peu de fois que possible :

$ echo one two 'three and four' | xargs ./show 
-> "one" "two" "three" "and" "four" 

et son comportement avec -n 1 :

$ echo one two 'three and four' | xargs -n 1 ./show 
-> "one" 
-> "two" 
-> "three" 
-> "and" 
-> "four" 

Si, d'autre part, la question initiale portait sur le point 1. le fractionnement de l'entrée et qu'elle devait être lue comme ceci (beaucoup de personnes venant ici semblent penser que c'est le cas, ou confondent les deux problèmes) :

Comment puis-je faire en sorte que xargs exécute la commande avec exactement un argument pour chaque ligne d'entrée donnée ? Son comportement par défaut est de fragmenter les lignes autour des espaces blancs .

alors la réponse est plus subtile.

On pourrait penser que -L 1 pourrait être utile, mais il s'avère qu'elle ne change pas l'analyse des arguments. Elle n'exécute la commande qu'une fois pour chaque ligne d'entrée, avec autant d'arguments qu'il y en avait sur cette ligne d'entrée :

$ echo $'one\ntwo\nthree and four' | xargs -L 1 ./show 
-> "one" 
-> "two" 
-> "three" "and" "four" 

De plus, si une ligne se termine par un espace, elle est ajoutée à la suivante :

$ echo $'one \ntwo\nthree and four' | xargs -L 1 ./show 
-> "one" "two" 
-> "three" "and" "four" 

Clairement, -L ne consiste pas à changer la façon dont xargs divise l'entrée en arguments.

Le seul argument qui le fait de manière multiplateforme (à l'exception des extensions GNU) est le suivant -0 qui divise l'entrée autour des octets NUL.

Ensuite, il suffit de traduire les nouvelles lignes en NUL à l'aide de la fonction tr :

$ echo $'one \ntwo\nthree and four' | tr '\n' '\0' | xargs -0 ./show 
-> "one " "two" "three and four" 

Maintenant, l'analyse des arguments semble correcte, y compris l'espacement de fin de ligne.

Enfin, si vous combinez cette technique avec -n 1 vous obtenez exactement une exécution de commande par ligne d'entrée, quelle que soit l'entrée que vous avez, ce qui peut être encore une autre façon de voir la question originale (peut-être la plus intuitive, étant donné le titre) :

$ echo $'one \ntwo\nthree and four' | tr '\n' '\0' | xargs -0 -n1 ./show 
-> "one " 
-> "two" 
-> "three and four" 

Comme mentionné ci-dessus, si vous utilisez GNU xargs vous pouvez remplacer le tr avec l'option spécifique à GNU -d :

$ echo $'one \ntwo\nthree and four' | xargs -d\\n -n1 ./show 
-> "one " 
-> "two" 
-> "three and four"

0 votes

Il semble que ce soit la meilleure réponse. cependant, je ne comprends toujours pas bien quelle est la différence entre -L et -n... pouvez-vous m'expliquer un peu plus ?

5 votes

@olala -L exécute la commande une fois par ligne d'entrée (mais un espace à la fin d'une ligne la joint à la ligne suivante, et la ligne est toujours divisée en arguments en fonction des espaces) ; while -n exécute la commande une fois par argument d'entrée. Si vous comptez le nombre de -> dans les exemples de sortie, ce sont le nombre de fois que le script ./show est exécuté.

0 votes

Je vois ! je n'avais pas réalisé qu'un espace à la fin d'une ligne la relie à la ligne suivante. merci !

24voto

tzot Points 32224

Si vous voulez exécuter la commande pour chaque ligne (c'est-à-dire le résultat) provenant de find alors de quoi avez-vous besoin ? xargs pour ?

Essayez :

find chemin -type f -exec votre-commande {} \;

où le littéral {} est remplacé par le nom de fichier et le littéral \; est nécessaire pour find pour savoir que la commande personnalisée s'arrête là.

EDIT :

(après l'édition de votre question clarifiant que vous êtes au courant de -exec )

Desde man xargs :

-L lignes max.
Utilisez au maximum lignes max. lignes d'entrée non vierges par ligne de commande. Les blancs qui suivent blancs de queue font qu'une ligne d'entrée est logiquement poursuivie sur la ligne d'entrée suivante. Implique -x.

Notez que les noms de fichiers se terminant par des blancs vous causeront des problèmes si vous utilisez xargs :

$ mkdir /tmp/bax; cd /tmp/bax
$ touch a\  b c\  c
$ find . -type f -print | xargs -L1 wc -l
0 ./c
0 ./c
0 total
0 ./b
wc: ./a: No such file or directory

Donc si vous ne vous souciez pas de la -exec il est préférable d'utiliser l'option -print0 y -0 :

$ find . -type f -print0 | xargs -0L1 wc -l
0 ./c
0 ./c
0 ./b
0 ./a

0 votes

J'ai trouvé didactiquement intéressant de faire précéder la commande de echo 'find . -type f -print0 | xargs -0L1 echo wc -l'. Vous pouvez alors facilement prévisualiser la commande que xargs va générer, par exemple voir la différence si vous utilisez '-0L2'.

23voto

Gray Points 58585

Comment puis-je faire en sorte que xargs exécute la commande exactement une fois pour chaque ligne d'entrée donnée ?

-L 1 est la solution la plus simple, mais elle ne fonctionne pas si l'un des fichiers contient des espaces. Il s'agit d'une fonction clé de find -print0 argument - pour séparer les arguments par ' \0 au lieu d'un espace. Voici un exemple :

echo "file with space.txt" | xargs -L 1 ls
ls: file: No such file or directory
ls: with: No such file or directory
ls: space.txt: No such file or directory

Une meilleure solution consiste à utiliser tr pour convertir les nouvelles lignes en nullité ( \0 ), puis utilisez les caractères xargs -0 argument. Voici un exemple :

echo "file with space.txt" | tr '\n' '\0' | xargs -0 ls
file with space.txt

Si vous devez ensuite limiter le nombre d'appels, vous pouvez utiliser la fonction -n 1 pour faire un appel au programme pour chaque entrée :

echo "file with space.txt" | tr '\n' '\0' | xargs -0 -n 1 ls

Cela vous permet également de filtrer la sortie de find avant en convertissant les ruptures en nulles.

find . -name \*.xml | grep -v /target/ | tr '\n' '\0' | xargs -0 tar -cf xml.tar

1 votes

Il y a une erreur de syntaxe dans le deuxième bloc de code tr''. \n ' ' \0\ => tr ' \n ' ' \0 J'ai essayé de corriger cela, mais "Les modifications doivent comporter au moins 6 caractères" (cela semble aussi stupide que le refus de git de commiter parce que ma modification comportait moins de 6 caractères).

1 votes

Qu'est-ce que cela signifie ? "Un autre problème avec l'utilisation -L est aussi qu'il ne permet pas d'arguments multiples pour chaque xargs appel de commande. " ?

0 votes

J'ai amélioré ma réponse pour supprimer cette information superflue @Moberg.

18voto

Alex Riedler Points 139

Ces deux méthodes fonctionnent également, et fonctionneront pour d'autres commandes qui n'utilisent pas find !

xargs -I '{}' rm '{}'
xargs -i rm '{}'

exemple de cas d'utilisation :

find . -name "*.pyc" | xargs -i rm '{}'

supprimera tous les fichiers pyc sous ce répertoire, même si les fichiers pyc contiennent des espaces.

0 votes

Cela émet un appel de service pour chaque élément, ce qui n'est pas optimal.

0 votes

@Gray : Qu'est-ce qu'un "appel de service" ?

0 votes

Cette réponse fonctionne mieux que les réponses les plus votées.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X