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


341

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

Sur 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. C'est plus efficace que cette version fonctionnellement équivalente:

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

Je sais que find a le drapeau "exec". Je cite juste un exemple illustratif d'une autre ressource.


4
Dans l'exemple que vous fournissez, find /path -type f -deleteserait encore plus efficace :)
tzot

essayez de ne pas utiliser xargs ...
Naib

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

Veuillez envisager de passer votre acceptation à la réponse de @ Tobia, ce qui est beaucoup mieux. La réponse acceptée ne gère pas les espaces dans les noms et ne permet pas plusieurs arguments à la commande xargs qui est l'une des principales fonctionnalités de xargs.
Gris

Réponses:


394

Les éléments suivants ne fonctionneront que si vous n'avez pas d'espace dans votre entrée:

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

à partir 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.

13
Pour moi, cela peut sortir comme xargs -n 1celui que vous avez donné montrait une "liste d'arguments trop longue".
Wernight

19
Si MAX-LINESest omis, il vaut 1 par défaut, donc cela xargs -lsuffit. Tu vois info xargs.
Thor

3
@Wernight: "-n1" ne donne pas 1 appel par ligne d'entrée. peut-être que votre ligne d'entrée était trop longue. demo: echo "foo bar" | xargs -n1 echo. par conséquent, si vous canalisez des éléments comme «ls», il ne gérera pas bien les espaces.
gatoatigrado

8
C'est faux. -L 1ne répond pas à la question d'origine, et -n 1ne le fait que dans l'une des interprétations possibles. Voir ma longue réponse ci-dessous.
Tobia

2
@Tobia: Il répond à la question d'origine, qui portait tout particulièrement sur les lignes d'entrée. C'est exactement ce qui -L 1fait. Pour moi, l'OP semblait clairement essayer d'éviter le comportement de segmentation par défaut, et puisque cela a été accepté, je suppose que j'avais raison. Votre réponse concerne un cas d'utilisation légèrement différent où vous souhaitez également le comportement de segmentation.
Draemon

207

Il me semble que toutes les réponses existantes sur cette page sont fausses, y compris celle marquée comme correcte. Cela découle 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 donnée", en passant la ligne entière (sans la nouvelle ligne) à la commande comme un seul argument, alors c'est la meilleure façon compatible UNIX de le faire:

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

GNU xargspeut ou non avoir des extensions utiles qui vous permettent de vous en débarrasser tr, mais elles ne sont pas disponibles sur OS X et d'autres systèmes UNIX.

Maintenant pour la longue explication…


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

  1. comment divise-t-il l'entrée en "arguments"; et
  2. le nombre d'arguments pour 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 le faire, 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'enregistrez comme showdans votre répertoire actuel et que vous le rendiez exécutable, voici comment cela fonctionne:

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

Maintenant, si la question d'origine concerne vraiment le point 2 ci-dessus (comme je pense que c'est le cas, après l'avoir lu plusieurs fois) et qu'elle doit être lue comme ceci (changements en gras):

Comment puis-je faire en sorte que xargs exécute la commande exactement une fois pour chaque argument d'entrée donné? Son comportement par défaut consiste à fragmenter l' entrée en arguments et à exécuter la commande le moins de fois possible , en passant 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 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'un autre côté, la question initiale portait sur le point 1, le fractionnement des entrées et qu'il devait être lu comme ceci (beaucoup de gens qui viennent ici semblent penser que c'est le cas, ou confondent les deux problèmes):

Comment puis-je faire exécuter xargs la commande avec exactement un argument pour chaque ligne d'entrée donnée? Son comportement par défaut consiste à découper les lignes autour des espaces .

alors la réponse est plus subtile.

On pourrait penser que cela -L 1pourrait être utile, mais il s'avère que cela ne change pas l'analyse des arguments. Il n'exécute la commande qu'une seule 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" 

Non seulement cela, mais 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" 

De toute évidence, il -Lne s'agit pas de changer la façon dont xargs divise l'entrée en arguments.

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

Ensuite, il suffit de traduire les nouvelles lignes en NUL à l'aide de 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 les espaces de fin.

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 une autre façon de regarder la question d'origine (peut-être la plus intuitive, compte tenu du titre):

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

ressemble à ceci est la meilleure réponse. cependant, je ne comprends toujours pas bien quelle est la différence entre -L et -n ... pouvez-vous expliquer un peu plus?
olala

5
@olala -Lexé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 selon les espaces); tandis que -nexécute la commande une fois par argument d'entrée. Si vous comptez le nombre de ->dans les exemples de sortie, il s'agit du nombre d' ./showexécutions du script .
Tobia

je vois! n'a pas réalisé qu'un espace à la fin d'une ligne le joint à la ligne suivante. Merci!
olala

4
GNU xargspeut ou non avoir des extensions utiles qui vous permettent de vous en débarrasser.tr Il a une telle extension très utile; from xargs --help- -d, --delimiter = CHARACTER les éléments du flux d'entrée sont séparés par CHARACTER, et non par des espaces; désactive le traitement des devis et des contre-obliques et le traitement EOF logique
Piotr Dobrogost

Cette réponse semble confuse en ce qui concerne -L. -Lne dit pas combien de fois exécuter le script par ligne, il dit combien de lignes de données d'entrée à consommer à la fois.
Moberg

22

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

Essayer:

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

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

ÉDITER:

(après la modification de votre question clarifiant ce que vous savez -exec)

De man xargs:

-L max-lines
Utilisez au plus max-lines des lignes d'entrée non vides par ligne de commande. Les espaces à la fin entraînent la poursuite logique d'une ligne d'entrée sur la ligne d'entrée suivante. Implique -x.

Notez que les noms de fichiers se terminant par des blancs vous causeraient 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 l' -execoption, vous feriez mieux d'utiliser -print0et -0:

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

18

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

-L 1est la solution simple mais cela ne fonctionne pas si l'un des fichiers contient des espaces. Il s'agit d'une fonction clé de l' -print0argument de find - pour séparer les arguments par le caractère «\ 0» au lieu d'espaces. 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 à trconvertir les sauts de ligne en caractères null ( \0), puis à utiliser l' xargs -0argument. 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 l' -n 1argument pour effectuer 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 de convertir les ruptures en valeurs nulles.

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

1
Il y a une erreur de syntaxe dans le deuxième bloc de code tr '\ n' '\ 0 \ => tr' \ n '' \ 0 ', j'ai essayé de résoudre ce problème mais "les modifications doivent comporter au moins 6 caractères" (cela semble stupide comme git refusant de s'engager parce que ma modification était inférieure à 6 caractères)
htaccess

1
Qu'est-ce que cela signifie: "Un autre problème avec l'utilisation -Lest également qu'il n'autorise pas plusieurs arguments pour chaque xargsappel de commande."?
Moberg

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

11

Une autre alternative ...

find /path -type f | while read ln; do echo "processing $ln"; done

9

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

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

exemple d'utilisation:

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

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


Cela émet un appel d'utilitaire pour chaque élément qui n'est pas optimal.
Gray

7
find path -type f | xargs -L1 command 

est tout ce dont vous avez besoin.


4

La commande suivante trouvera tous les fichiers (-type f) dans /path, puis les copiera en utilisant cpdans le dossier actuel. Notez l'utilisation de if -I %pour spécifier un caractère d'espace réservé dans la cpligne de commande afin que les arguments puissent être placés après le nom du fichier.

find /path -type f -print0 | xargs -0 -I % cp % .

Testé avec xargs (GNU findutils) 4.4.0


2

Vous pouvez limiter le nombre de lignes ou d'arguments (s'il y a des espaces entre chaque argument) à l'aide des indicateurs --max-lines ou --max-args, respectivement.

  -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.

  --max-lines[=max-lines], -l[max-lines]
         Synonym  for  the -L option.  Unlike -L, the max-lines argument is optional.  If max-args is not specified, it defaults to one.  The -l option
         is deprecated since the POSIX standard specifies -L instead.

  --max-args=max-args, -n max-args
         Use at most max-args arguments per command line.  Fewer than max-args arguments will be used if the size (see  the  -s  option)  is  exceeded,
         unless the -x option is given, in which case xargs will exit.

0

Il semble que je n'ai pas assez de réputation pour ajouter un commentaire à la réponse de Tobia ci - dessus , donc j'ajoute cette "réponse" pour aider ceux d'entre nous qui veulent expérimenter de xargsla même manière sur les plates-formes Windows.

Voici un fichier batch Windows qui fait la même chose que le script "show" codé rapidement de Tobia:

@echo off
REM
REM  cool trick of using "set" to echo without new line
REM  (from:  http://www.psteiner.com/2012/05/windows-batch-echo-without-new-line.html)
REM
if "%~1" == "" (
    exit /b
)

<nul set /p=Args:  "%~1"
shift

:start
if not "%~1" == "" (
    <nul set /p=, "%~1"
    shift
    goto start
)
echo.

0

Les réponses de @Draemon semblent correctes avec "-0" même avec de l'espace dans le fichier.

J'essayais la commande xargs et j'ai trouvé que "-0" fonctionnait parfaitement avec "-L". même les espaces sont traités (si l'entrée était terminée par null). Ce qui suit est un exemple :

#touch "file with space"
#touch "file1"
#touch "file2"

Ce qui suit divisera les valeurs nulles et exécutera la commande sur chaque argument de la liste:

 #find . -name 'file*' -print0 | xargs -0 -L1
./file with space
./file1
./file2

donc -L1exécutera l'argument sur chaque caractère nul terminé si elle est utilisée avec « -0 ». Pour voir la différence, essayez:

 #find . -name 'file*' -print0 | xargs -0 | xargs -L1
 ./file with space ./file1 ./file2

même cela s'exécutera une fois:

 #find . -name 'file*' -print0  | xargs -0  | xargs -0 -L1
./file with space ./file1 ./file2

La commande s'exécutera une fois car le "-L" ne se divise plus sur l'octet nul. vous devez fournir "-0" et "-L" pour fonctionner.


-3

Dans votre exemple, le fait de canaliser la sortie de find vers xargs est que le comportement standard de l'option -exec de find consiste à exécuter la commande une fois pour chaque fichier trouvé. Si vous utilisez find et que vous voulez son comportement standard, la réponse est simple - n'utilisez pas xargs pour commencer.


En fait, ce que je peux impliquer des modifications du PO, c'est que les données d'entrée n'ont rien à voir avec find, et c'est pourquoi ils ne préfèrent pas l' -execoption.
tzot

-3

exécutez ant task clean-all sur chaque build.xml du dossier en cours ou du sous-dossier.

find . -name 'build.xml' -exec ant -f {} clean-all \;

Tout le monde n'a pas antinstallé.
Gray
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.