comment passer le résultat de `find` comme une liste de fichiers?

11

La situation est, j'ai un lecteur MP3 mpg321qui accepte une liste de fichiers comme argument. Je garde ma musique dans un répertoire nommé "musique", dans lequel il y a quelques autres répertoires. Je veux juste les jouer tous, donc je lance le programme avec

mpg321 $(find /music -iname "*\.mp3")

. Le problème est que certains noms de fichiers contiennent des espaces, et le programme divise ces noms en parties plus petites et se plaint des fichiers manquants. Envelopper le résultat de findentre guillemets

mpg321 "$(find /music -iname "*\.mp3")"

n'aide pas car tout deviendra un grand "nom de fichier", qui est évidemment introuvable.

Comment puis-je faire cela alors? Si cela importe, j'utilise bash, mais je passerai zshbientôt.

phunehehe
la source

Réponses:

17

Essayez d'utiliser find's -print0ou l' -printfoption en combinaison avec xargscomme ceci:

find /music -iname "*\.mp3" -print0 | xargs -0 mpg321

Comment cela fonctionne est expliqué par la page de manuel de find :

-print0

Vrai; affiche le nom de fichier complet sur la sortie standard, suivi d'un caractère nul (au lieu du caractère de nouvelle ligne utilisé par -print). Cela permet aux noms de fichiers qui contiennent des sauts de ligne ou d'autres types d'espaces blancs d'être correctement interprétés par les programmes qui traitent la sortie de recherche. Cette option correspond à l'option -0 de xargs.

Steven D
la source
Désolé pour toute la retouche. Il me semble que le motif -print0 xarg -0 a échoué sur d'autres types d'espaces, mais je n'ai pas pu trouver d'exemple.
Steven D
oh oui ça marche! mais je me demande toujours pourquoi mpg321 $(find /music -iname "*\.mp3" -print0)pas?
phunehehe
1
Eh bien, je crois que cela ne fonctionne pas pour deux raisons: (1) les noms de fichiers sont séparés par des caractères nuls, ce qui n'est pas du tout ce que mpg321 attend et (2) xargs fait le travail d'échapper à l'autre espace blanc, non trouver.
Steven D
2
@phunehehe, @Steven: mpg321n'a rien à voir avec ça, c'est le shell qui décompose la sortie de finden arguments séparés. Et -print0 | xargs -0fonctionnera avec tous les noms de fichiers possibles.
Gilles 'SO- arrête d'être méchant'
8
find /music -iname "*\.mp3" -exec mpg123 {} +

Avec GNU find, vous pouvez également utiliser -print0etxargs -0 , mais cela ne sert à rien d'apprendre un autre outil. La -exec ... {} +syntaxe est peu mentionnée car Linux l'a acquise plus tard -print0, mais il n'y a aucune raison de ne pas l'utiliser maintenant.

Avec zsh ou bash 4, c'est beaucoup plus simple:

mpg123 **/*.[Mm][Pp]3

Dans zsh uniquement, vous pouvez rendre un (partie d'un) motif insensible à la casse:

mpg123 (#i)**/*.mp3
Gilles 'SO- arrête d'être méchant'
la source
+1 à cela, "find -print0" est un vilain hack.
p-statique
2

Je pense que la solution de Steven est la meilleure, mais une autre façon consiste à utiliser le -Idrapeau de xargs , qui vous permet de spécifier une chaîne qui sera ensuite remplacée dans la commande par l'argument (au lieu de simplement ajouter l'argument à la fin de la commande). Vous pouvez l'utiliser pour citer l'argument:

find /music -iname "*\.mp3" | xargs -0 -Ifoo mpg321 "foo"
Michael Mrozek
la source
Je ne comprends pas cette réponse ... Vous utilisez le -0drapeau de xargs sans trouver -print0. De plus, le -Idrapeau de xargs a un certain nombre de conséquences. Contrairement à tout simplement xargsqui compressera toutes les lignes de stdin en une seule commande, xargs -Is'exécutera mpg321potentiellement des centaines ou des milliers de fois (une fois pour chaque fichier), ce qui n'est certainement pas l'intention. Gardez également à l'esprit qu'il n'est pas nécessaire de citer foo, tout comme xargscela en interne. Notez que si vous compressez tous les noms de fichiers sur la ligne et que vous les envoyez xargs -I, ils ne s'ouvriront pas.
Six
1

Il est généralement préférable de le faire directement, -exec ${tgt_process} \{\} +mais si vous avez besoin d'obtenir une liste de noms de fichiers délimitée de manière fiable dans un fichier ou un flux findpour une raison quelconque, vous pouvez le faire:

find -exec sh -c 'printf "///%s///\n" "$@"' -- \{\} +

Ce que vous en retirez, ce sont deux chaînes uniques . En tête de chaque nom de fichier se trouve la chaîne \n///et à la fin de chaque nom de fichier se trouve la chaîne ///\n. Ces deux chaînes n'apparaissent nulle part ailleurs dans findla sortie de, à l'exception de ces positions, quels que soient les caractères contenus dans les noms de fichiers.

En outre, l'utilisation ci-dessus est portable POSIX de base et peut être utilisée pour fonctionner sur presque tous les systèmes Unix. Ce n'est pas le cas de l'utilisation d'un délimiteur d'octets nuls - malgré sa commodité - recommandé par certains autres.

Mais, encore une fois, cela n'est nécessaire que si vous ne pouvez pas directement le -execfaire $tgt_processpour une raison quelconque, car cela devrait être votre objectif. D'une part, la méthode ci-dessus nécessite toujours l'analyse. Par exemple, si vous voulez que chaque shell de nom de fichier soit cité, vous devez d'abord vous assurer que les guillemets durs dans le nom de fichier ont été échappés:

find ... + | sed 's|'\''|&"&"&|g;s|///|'\''|g'

Cela génère un tableau de noms de fichiers correctement échappés du shell, quels que soient leurs caractères constitutifs. Il ne vous reste plus qu'à espérer que votre application côté réception ne la gâchera pas.

mikeserv
la source
0

Une autre façon de procéder consiste à échapper à tous les caractères spéciaux qui se trouvent dans vos noms de fichiers. Par exemple:

find /music -iname "*\.mp3" | sed 's!\([] \*\$\/&[]\)!\\\1!g' | xargs mpg321

Cela passera essentiellement les noms de fichiers correctement échappés à xargs pour exécution et il n'y aura aucun problème.

Priyabrata Patnaik
la source
1
Cela rompt toujours sur les nouvelles lignes dans les noms de fichiers. Et vous pourriez tout aussi bien sed 's|.|\\&|g'- c'est ce que POSIX recommande de toute façon.
mikeserv