web-dev-qa-db-fra.com

Quelle est la difference entre find with -exec et xargs?

en essayant d'apprendre les scripts Bash, je souhaite exécuter une commande sur tous les fichiers situés sous mon répertoire actuel qui répondent à une certaine condition. En utilisant

find -name *.flac

Plus précisément, je veux convertir .flac en .mp3. Je peux trouver tous les fichiers. Cependant, je ne vois pas la différence dans l'exécution d'une commande utilisant l'option -exec pour find et l'utilisation de xargs. Par exemple.

find -name *.flac | xargs -i ffmpeg -i {} {}.mp3

par rapport à

find -name *.flac -exec ffmpeg -i {} {}.mp3 \;

Quelqu'un peut-il souligner la différence? Quel est le meilleur praticice? Quels sont les avantages/inconvénients?

Aussi: si je voulais supprimer simultanément le fichier d'origine, comment pourrais-je ajouter une deuxième commande dans le code ci-dessus?

6
Suppenkasper

Sommaire:

Sauf si vous êtes beaucoup plus familiarisé avec xargs que _-exec_, vous voudrez probablement utiliser _-exec_ lorsque vous utilisez find.

Étant donné que xargs est un programme séparé, son appel risque d'être légèrement moins efficace que d'utiliser _-exec_, caractéristique du programme find. Nous ne souhaitons généralement pas appeler un programme supplémentaire s'il n'apporte aucun avantage supplémentaire en termes de fiabilité, de performance ou de lisibilité. Puisque _find ... -exec ..._ permet d'exécuter des commandes avec une liste d'arguments (comme le fait xargs) si possible, l'utilisation de xargs avec find sur _-exec_ n'est pas vraiment un avantage. Dans le cas de ffmpeg, nous devons spécifier les fichiers d'entrée et de sortie. Par conséquent, nous ne pouvons pas obtenir de gains de performance en utilisant l'une ou l'autre méthode pour construire une liste d'arguments, et avec xargs, supprimer l'extension illogique du nom de fichier d'origine est plus difficile.

Que fait xargs

Remarque: L'indicateur détaillé (qui affiche la commande construite avec ses arguments) dans xargs est _-t_ et l'indicateur interactif (qui provoque la l’utilisateur est invité à confirmer l’opération sur chaque argument) est _-p_. Vous pouvez trouver ces deux éléments utiles pour comprendre et tester son comportement.

xargs tente de transformer son STDIN (généralement le STDOUT de la commande précédente qui lui a été transmise) en une liste d'arguments à une commande.

_command1 | xargs command2 [output of command1 will be appended here]_

Puisque STDOUT ou STDIN est juste un flux de texte (c’est aussi pourquoi vous ne devez pas analyser le résultat de ls), xargs est facilement déclenché. Il lit les arguments comme étant délimités par des espaces ou des nouvelles lignes. Les noms de fichiers sont autorisés à contenir des espaces et peuvent même contenir des nouvelles lignes. De tels noms de fichier provoqueront un comportement inattendu. Disons que vous avez un fichier appelé _foo bar_. Lorsqu'une liste contenant ce nom de fichier est transmise à xargs, il tente d'exécuter la commande donnée sur foo et sur bar.

Le même problème se produit lorsque vous tapez _command foo bar_ et vous savez que vous pouvez l'éviter en citant l'espace ou le nom complet, par exemple _command foo\ bar_ ou _command "foo bar"_, mais même si nous pouvons citer la liste passée à xargs nous ne le souhaitons généralement pas, car nous ne voulons pas que toute la liste soit traitée comme un seul argument. La solution standard consiste à utiliser le caractère null comme délimiteur, car les noms de fichiers ne peuvent pas le contenir:

find pathtest(s) -print0 | xargs -0 command

Cela provoque find pour ajouter le caractère null à chaque nom de fichier au lieu d'un espace, et xargs pour ne traiter que le caractère nul comme délimiteur.

Des problèmes peuvent toujours se produire si la commande n'accepte pas plusieurs arguments ou si la liste des arguments est extrêmement longue.

Dans ce cas, vous utilisez ffmpeg, qui attend que les fichiers d'entrée soient spécifiés en premier et les fichiers de sortie en dernier. Nous pouvons indiquer à ffmpeg le ou les fichiers à utiliser comme entrée de manière explicite avec le drapeau _-i_, mais nous devons également indiquer le nom du fichier de sortie (à partir duquel le format est généralement deviné, bien que nous puissions également le spécifier). Ainsi, pour construire des commandes appropriées, vous devez utiliser l'option de remplacement de la chaîne (_-I_ ou _-i_) de xargs pour spécifier les fichiers d'entrée et de sortie:

_... | xargs -I{} command {} {}.out_

(la documentation indique que _-i_ est obsolète à cette fin et nous devrions utiliser _-I_ à la place, mais je ne sais pas pourquoi. Lorsque vous utilisez _-I_, vous devez spécifier le remplacement (_{}_ est normalement utilisé) immédiatement après l’option. Avec _-i_, vous pouvez omettre de spécifier le remplacement, mais _{}_ est compris par défaut.)

L'option _-I_ entraîne le fractionnement de la liste de commandes uniquement sur les nouvelles lignes et non sur les espaces. Par conséquent, si vous êtes certain que vos noms de fichiers ne contiendront pas de nouvelles lignes, vous n'avez pas besoin d'utiliser _-print0 | xargs -0_ lorsque vous utilisez _-I_. Si vous avez des doutes, vous pouvez toujours utiliser la syntaxe plus sûre:

_find -name "*.flac" -print0 | xargs -0I{} ffmpeg -i {} {}.mp3
_

Cependant, l’avantage en performances de xargs (qui nous permet d’exécuter une commande une seule fois avec une liste d’arguments) est perdu ici, car ffmpeg doit être exécuté une fois pour chaque paire de fichiers d’entrée et de sortie (vous pouvez le voir facilement en ajoutant echo à ffmpeg pour tester la commande ci-dessus). Cela produit également un nom de fichier illogique et ne vous permet pas d'exécuter plusieurs commandes. Pour ce faire, vous pouvez appeler bash, comme dans réponse du dessert :

_... | xargs -I{} bash -c 'ffmpeg -i {} {}.mp3 && rm {}'
_

mais renommer est délicat .

Comment _-exec_ est différent

Lorsque vous utilisez l'option _-exec_ sur find, les fichiers trouvés sont transmis sous forme d'arguments à la commande après _-exec_. Ils ne sont pas transformés en texte. Avec la syntaxe:

_find ... -exec command {} \;_

command est exécuté une fois pour chaque fichier trouvé. Avec la syntaxe

_find ... -exec command {} +_

une liste d'arguments est construite à partir des fichiers trouvés afin que nous puissions exécuter la commande une seule fois (ou seulement autant de fois que nécessaire) sur plusieurs fichiers, ce qui confère l'avantage en performances de xargs. Cependant, étant donné que les arguments de nom de fichier ne sont pas construits à partir d'un flux de texte, utiliser _-exec_ ne pose pas le problème que xargs pose sur les espaces et autres caractères spéciaux.

Avec ffmpeg, nous ne pouvons pas utiliser _+_ pour la même raison que xargs n’a apporté aucun avantage en termes de performances; comme nous devons spécifier les entrées et les sorties, la commande doit être exécutée sur chaque fichier individuellement. Nous devons utiliser une forme de

_find -name "*.flac" -exec ffmpeg -i {} {}.out \;
_

Ceci, encore une fois, vous donnera un fichier assez illogiquement nommé, comme la réponse de dessert explique , alors vous voudrez peut-être la supprimer, car la réponse de dessert explique comment faire avec la manipulation de chaîne (pas facilement réalisable dans xargs; une autre raison d'utiliser _-exec_). Il explique également comment exécuter plusieurs commandes sur le fichier afin de pouvoir supprimer en toute sécurité le fichier d'origine après une conversion réussie.

Au lieu de répéter la recommandation de dessert, avec laquelle je suis d’accord, je proposerai une solution de remplacement à find, qui offre une souplesse similaire à celle utilisée pour exécuter _bash -c_ après _-exec_; une boucle bash for:

_shopt -s globstar           # allow recursive globbing with **
for f in ./**/*.flac; do    # for all files ending with .flac
   # convert them, stripping the original extension from the new filename
   echo ffmpeg -i "$f" "${f%.flac}.mp3" &&
   echo rm -v "$f"          # if that succeeded, delete the original
done
shopt -u globstar           # turn recursive globbing off
_

Supprimez echoes après les tests pour réellement utiliser les fichiers.

ffmpeg ne reconnaît pas _--_ pour marquer la fin des options. Par conséquent, pour éviter que les noms de fichiers commençant par _-_ ne soient interprétés comme des options, nous utilisons _./_ au lieu de commencer par _**_, de sorte que tous les chemins commencent par _./_ au lieu de noms de fichiers arbitraires. Cela signifie que nous n'avons pas besoin d'utiliser _--_ avec rm (qui le reconnaît).


Remarque : vous devez citer votre expression de test _-name_ si elle contient des caractères génériques, sinon le shell les développera si possible (c'est-à-dire s'ils correspond à tous les fichiers du répertoire en cours) avant qu’ils ne soient passés à find, utilisez donc

_find -name "*.flac"
_

pour prévenir les comportements inattendus.

9
Zanna

Généralement, on essaie d'appeler le moins de commandes possible, mais dans votre cas, je pense que c'est une question de goût - je choisirais -exec, en l'utilisant comme suit:

find . -name '*.flac' -exec bash -c 'ffmpeg -i "$0" "${0%flac}mp3" && rm "$0"' {} \;

L'astuce consiste à appeler bash avec l'option -c. Ainsi, vous pouvez non seulement exécuter plusieurs commandes, mais également utiliser substitution de paramètre Bash pour supprimer la fin de flac. de vos noms de fichiers - Je suppose que vous ne voulez pas vraiment vous retrouver avec des fichiers nommés nomfichier.flac.mp3 , et vous?

Des explications

  • bash -c '…' {} - lance la ou les commandes dans bash avec le nom de fichier comme premier argument (accessible avec $0)
  • ${0%flac} - supprime flac de la fin du nom de fichier
  • && rm "$0" - uniquement si la commande précédente a réussi, supprime le fichier d'origine
4
dessert

Comme Zanna et le dessert déjà répondu -exec devrait être préféré lorsque xargs n'est pas nécessaire ( "Nous ne voulons généralement pas appeler un programme supplémentaire s'il ne le fait pas fournir des avantages supplémentaires en termes de fiabilité, de performance ou de lisibilité. ")

Bien que cela soit tout à fait correct, je veux ajouter que xargs en combinaison avec le drapeau -P peut offrir un avantage substantiel en termes de performance.

xargs créera les processus en parallèle permettant le multi-threading, similaire mais plus souple que la commande parallel.

-P max-procs, --max-procs=max-procs
              Run up to max-procs processes at a time; the default is 1.  If max-procs is 0, xargs will run as many processes as possible at a time.  Use the -n option or the -L option with -P; other‐
              wise chances are that only one exec will be done. 
              [...]

Cela est particulièrement utile pour les processus qui ne s'exécutent pas par eux-mêmes. Dans votre cas, ffmpeg se chargera du multithreading, ce qui n’aidera pas ou aura même un effet négatif sur les performances.

find . -name "*.ext" -print0 | xargs -0 -i -P 20 command -in {} -out {}.out
2
pLumo