find + xargs: ligne d'argument trop longue

21

J'ai une ligne comme la suivante:

find /foo/bar -name '*.mp4' -print0 | xargs -i {} -0 mv -t /some/path {}

mais j'ai eu l'erreur suivante:

xargs: argument line too long

Je suis confus. L'utilisation de n'est-elle pas xargscensée aider précisément avec ce problème?

Remarque: je sais que je peux utiliser techniquement -execdans find, mais je voudrais comprendre pourquoi ce qui précède échoue, car je crois que xargsc'est censé savoir comment diviser l'entrée en une taille gérable selon l'argument qu'elle s'exécute. N'est-ce pas vrai?

Tout cela avec zsh.

Amelio Vazquez-Reina
la source

Réponses:

11

Eh bien, pour une chose, le -icommutateur est obsolète:

-i[replace-str]
     This  option  is a synonym for -Ireplace-str if replace-str is specified. 
     If the replace-str argument is missing, the effect is the same as -I{}. 
     This option is deprecated; use -I instead.

Donc, quand j'ai changé votre commande en cela, cela a fonctionné:

$ find /foo/bar -name '*.mp4' -print0 | xargs -I{} -0 mv -t /some/path {}

Exemple

$ find . -print0 | xargs -I{} -0 echo {}
.
./.sshmenu
./The GIT version control system.html
./.vim_SO
./.vim_SO/README.txt
./.vim_SO/.git
./.vim_SO/.git/objects
./.vim_SO/.git/objects/pack
./.vim_SO/.git/objects/pack/pack-42dbf7fe4a9b431a51da817ebf58cf69f5b7117b.idx
./.vim_SO/.git/objects/pack/pack-42dbf7fe4a9b431a51da817ebf58cf69f5b7117b.pack
./.vim_SO/.git/objects/info
./.vim_SO/.git/refs
./.vim_SO/.git/refs/tags
...

Utilisation de -I{}

Cette approche ne doit pas être utilisée depuis l'exécution de cette construction de commande:

$ find -print0 ... | xargs -I{} -0 ...

active implicitement ces commutateurs vers xargs, -xet -L 1. Le -L 1configure xargspour qu'il appelle les commandes que vous souhaitez qu'il exécute les fichiers d'une seule manière.

Donc, cela va à l'encontre du but de l'utilisation xargsici, car si vous lui donnez 1000 fichiers, il va exécuter la mvcommande 1000 fois.

Alors, quelle approche dois-je utiliser alors?

Vous pouvez le faire en utilisant des xargs comme celui-ci:

$ find /foot/bar/ -name '*.mp4' -print0 | xargs -0 mv -t /some/path

Ou tout simplement trouver trouver tout faire:

$ find /foot/bar/ -name '*.mp4' -exec mv -t /some/path {} +
slm
la source
Merci! Quand vous avez dit "This approach shouldn't be used"quelle approche utiliser à la place? Serait "find /foot/bar/ -name '*.csv' -print0 | xargs -0 mv -t some_dir'"une meilleure solution? Si oui, comment ne xargssait dans ce cas où la mvcommande à l' alimentation dans les arguments qu'il obtient de la conduite? (les place-t-il toujours en dernier?)
Amelio Vazquez-Reina
@ user815423426 - Le faire avec juste le find ... -exec ...est une meilleure façon ou si vous voulez utiliser xargsle find ... | xargs ... mv -t ...c'est bien aussi. Ouais ça les met toujours en dernier. C'est pourquoi cette méthode a besoin de -t.
slm
5

L'option -iprend un argument facultatif. Puisque vous mettez un espace après -i, il n'y avait aucun argument à l' -ioption et donc le suivant -0n'était pas une option pour xargsmais le deuxième des 6 opérandes {} -0 mv -t /some/path {}.

Avec seulement cette option -i, xargs attendait une liste de noms de fichiers séparés par des sauts de ligne. Puisqu'il n'y avait probablement pas de nouvelle ligne dans l'entrée, xargs a reçu ce qui ressemblait à un énorme nom de fichier (avec des octets nuls intégrés, mais xargs n'a pas vérifié cela). Cette chaîne unique contenant la sortie entière de findétait plus longue que la longueur de ligne de commande maximale, d'où l'erreur «ligne de commande trop longue».

Votre commande aurait fonctionné avec -i{}au lieu de -i {}. Alternativement, vous auriez pu utiliser -I {}: -Iest similaire à -i, mais prend un argument obligatoire, donc l'argument suivant passé à xargsest utilisé comme argument de l' -Ioption. Ensuite, l'argument qui suit -0est interprété comme une option, etc.

Cependant, vous ne devriez pas utiliser -I {}du tout. L'utilisation -Ia trois effets:

  • -Idésactive le traitement des devis, ce qui est -0déjà le cas.
  • -Ichange la chaîne à remplacer, mais {}est la valeur par défaut.
  • -Ientraîne l'exécution de la commande séparément pour chaque enregistrement d'entrée, ce qui est inutile ici car votre commande ( mv -t) est spécifiquement destinée à gérer plusieurs fichiers par appel.

Soit laisser tomber -Iet -itout à fait

find /foo/bar -name '*.mp4' -print0 | xargs -0 mv -t /some/path {}

ou supprimez xargs et utilisez -exec:

find /foo/bar -name '*.mp4' -exec mv -t /some/path {} +
Gilles 'SO- arrête d'être méchant'
la source
0

Essayez d'utiliser une boucle bash for:

for FILE in *.mp4 ; do rm $FILE ; done

ou si vous souhaitez voir ce qui se passe:

for FILE in *.mp4 ; do echo Removing $FILE ; rm $FILE ; done
C. Shamis
la source
0

Si vous voyez cela en utilisant la coquille de poisson .
Cela se rapporte à la façon dont le poisson étend la chaîne de remplacement{}

Si vous utilisez du poisson, vous devez échapper à la chaîne de remplacement \{\}

| xargs -I \{\} echo \{\}

ou utilisez une chaîne de remplacement différente

| xargs -I ! echo !
nelaaro
la source