Comment puis-je faire en sorte que xargs exécute la commande exactement une fois pour chaque ligne d'entrée donnée?
De 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 cette trouvaille a le drapeau "exec". Je viens de citer un exemple illustratif d'une autre ressource.
Ce qui suit ne fonctionnera que si vous n'avez pas d'espaces dans votre entrée:
xargs -L 1
xargs --max-lines=1 # synonym for the -L option
depuis 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.
Il me semble que toutes les réponses existantes sur cette page sont fausses, y compris celle marquée comme correcte. Cela provient 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," passez la ligne entière (sans la nouvelle ligne) à la commande sous la forme d'un argument unique, alors c’est la meilleure façon de le faire compatible avec UNIX:
... | tr '\n' '\0' | xargs -0 -n1 ...
GNU xargs
peut avoir ou non des extensions utiles vous permettant de supprimer tr
, mais elles ne sont pas disponibles sur OS X ni sur 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:
Pour tester le comportement de xargs, nous avons besoin d'un utilitaire indiquant 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 dans bash:
#!/bin/bash
echo -n "-> "; for a in "$@"; do echo -n "\"$a\" "; done; echo
En supposant que vous enregistrez le sous le nom show
dans 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 initiale concerne vraiment le point 2. ci-dessus (comme je le pense, après l'avoir relue plusieurs fois) et qu'il se lise comme suit (changements en gras):
Comment puis-je faire en sorte que xargs exécute la commande exactement une fois pour chaque argument de l'entrée indiquée? Son comportement par défaut est de fragmenter l'entrée en arguments et d'exécuter la commande aussi peu de fois que 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 en 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, en revanche, la question initiale concernait le fractionnement des entrées du point 1. et que cela devait être lu comme suit (beaucoup de personnes qui arrivent ici semblent penser que c'est le cas ou confondent les deux problèmes):
Comment 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 découper les lignes autour des espaces .
alors la réponse est plus subtile.
On pourrait penser que -L 1
pourrait ê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"
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 -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 finaux.
Enfin, si vous combinez cette technique avec -n 1
, vous obtenez exactement une commande par ligne d'entrée, quelle que soit l'entrée que vous avez, ce qui peut être un autre moyen de regarder la question initiale (probablement la plus intuitive, à partir du titre):
$ echo $'one \ntwo\nthree and four' | tr '\n' '\0' | xargs -0 -n1 ./show
-> "one "
-> "two"
-> "three and four"
Si vous voulez exécuter la commande pour toutes les lignes (c'est-à-dire les résultats) provenant de find
, alors pourquoi avez-vous besoin de xargs
?
Essayer:
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 se termine ici.
(après la modification de votre question précisant que vous connaissez -exec
)
De man xargs
:
-L max-lines
Utilisez au plus max-lignes lignes d'entrée non vides par ligne de commande. Trailing les blancs font que la ligne d’entrée continue logiquement sur la ligne d’entrée suivante . Implique -x.
Notez que les noms de fichiers se terminant par des blancs vous poseraient des problèmes si vous utilisiez 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'option -exec
, vous feriez mieux d'utiliser -print0
et -0
:
$ find . -type f -print0 | xargs -0L1 wc -l
0 ./c
0 ./c
0 ./b
0 ./a
Comment puis-je faire en sorte que xargs exécute la commande exactement une fois pour chaque ligne d'entrée donnée?
Je n'utilise pas -L 1
answer car il ne gère pas les fichiers contenant des espaces, fonction clé de find -print0
.
echo "file with space.txt" | xargs -L 1 ls
ls: file: No such file or directory
ls: space.txt: No such file or directory
ls: with: No such file or directory
Une meilleure solution consiste à utiliser tr
pour convertir les nouvelles lignes en caractères nuls (\0
), puis à utiliser l'argument xargs -0
.
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'argument -n 1
pour passer 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 before convertissant les sauts en nulls.
find . -name \*.xml | grep -v /workspace/ | tr '\n' '\0' | xargs -0 tar -cf xml.tar
Une autre alternative ...
find /path -type f | while read ln; do echo "processing $ln"; done
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.
find path -type f | xargs -L1 command
c'est tout ce dont vous avez besoin.
La commande suivante trouvera tous les fichiers (-type f) dans /path
et les copiera ensuite avec cp
dans le dossier actuel. Notez use if -I %
pour spécifier un caractère de substitution dans la ligne de commande cp
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
Vous pouvez limiter le nombre de lignes ou d'arguments (s'il existe des espaces entre chaque argument) en utilisant les 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.
Les réponses de @Draemon semblent être exactes 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 un null). Ce qui suit est un exemple :
#touch "file with space"
#touch "file1"
#touch "file2"
Ce qui suit divisera les valeurs NULL et exécutera la commande sur chaque argument de la liste:
#find . -name 'file*' -print0 | xargs -0 -L1
./file with space
./file1
./file2
so -L1
exécutera l'argument sur chaque caractère terminé par un caractère nul s'il est utilisé 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 sera exécutée une fois, car le "-L" ne se divise plus en octet nul. vous devez fournir à la fois "-0" et "-L" pour fonctionner.
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 souhaitent expérimenter xargs
de la même manière sur les plateformes 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.