web-dev-qa-db-fra.com

Pourquoi le bouclage sur la sortie de find est-il une mauvaise pratique?

Cette question est inspirée par

Pourquoi l'utilisation d'une boucle Shell pour traiter du texte est-elle considérée comme une mauvaise pratique?

Je vois ces constructions

for file in `find . -type f -name ...`; do smth with ${file}; done

et

for dir in $(find . -type d -name ...); do smth with ${dir}; done

être utilisé ici presque quotidiennement même si certaines personnes prennent le temps de commenter ces messages expliquant pourquoi ce genre de choses devrait être évité ...
En voyant le nombre de ces messages (et le fait que parfois ces commentaires sont simplement ignorés), j'ai pensé que je pourrais aussi bien poser une question:

Pourquoi le bouclage sur la sortie de find est-il une mauvaise pratique et quelle est la bonne façon d'exécuter une ou plusieurs commandes pour chaque nom/chemin de fichier renvoyé par find?

176
don_crissti

Le problème

for f in $(find .)

combine deux choses incompatibles.

find imprime une liste de chemins de fichiers délimités par des caractères de nouvelle ligne. Alors que l'opérateur split + glob qui est invoqué lorsque vous laissez cette $(find .) sans guillemets dans ce contexte de liste le divise sur les caractères de $IFS (Inclut par défaut la nouvelle ligne, mais aussi l'espace et la tabulation (et NUL dans zsh)) et effectue la globalisation sur chaque mot résultant (sauf dans zsh) (et même l'expansion d'accolade dans les dérivés ksh93 ou pdksh!).

Même si vous le faites:

IFS='
' # split on newline only
set -o noglob # disable glob (also disables brace expansion in pdksh
              # but not ksh93)
for f in $(find .) # invoke split+glob

C'est toujours faux car le caractère de nouvelle ligne est aussi valide que n'importe quel autre dans un chemin de fichier. La sortie de find -print N'est tout simplement pas post-traitable de manière fiable (sauf en utilisant une astuce compliquée, comme indiqué ici ).

Cela signifie également que le shell doit stocker entièrement la sortie de find, puis la diviser + la globaliser (ce qui implique de stocker cette sortie une deuxième fois en mémoire) avant de commencer à boucler sur les fichiers.

Notez que find . | xargs cmd A des problèmes similaires (là, les blancs, les retours à la ligne, les guillemets simples, les guillemets doubles et les barres obliques inverses (et avec certains xarg octets d'implémentation ne faisant pas partie de caractères valides) sont un problème)

Alternatives plus correctes

La seule façon d'utiliser une boucle for sur la sortie de find serait d'utiliser zsh qui prend en charge IFS=$'\0' Et:

IFS=$'\0'
for f in $(find . -print0)

(remplacez -print0 par -exec printf '%s\0' {} + pour les implémentations de find qui ne prennent pas en charge le non standard (mais assez courant de nos jours) -print0).

Ici, la manière correcte et portable est d'utiliser -exec:

find . -exec something with {} \;

Ou si something peut accepter plusieurs arguments:

find . -exec something with {} +

Si vous avez besoin que cette liste de fichiers soit gérée par un shell:

find . -exec sh -c '
  for file do
    something < "$file"
  done' find-sh {} +

(attention, il peut en démarrer plusieurs sh).

Sur certains systèmes, vous pouvez utiliser:

find . -print0 | xargs -r0 something with

bien que cela ait peu d'avantage sur la syntaxe standard et signifie que somethingstdin est soit le tube, soit /dev/null.

Une des raisons que vous voudrez peut-être utiliser pourrait être d'utiliser l'option -P De GNU xargs pour le traitement parallèle. Le problème stdin peut également être travaillé avec GNU xargs avec l'option -a avec des shells supportant la substitution de processus:

xargs -r0n 20 -P 4 -a <(find . -print0) something

par exemple, pour exécuter jusqu'à 4 appels simultanés de something chacun prenant 20 arguments de fichier.

Avec zsh ou bash, une autre façon de parcourir la sortie de find -print0 Est avec:

while IFS= read -rd '' file <&3; do
  something "$file" 3<&-
done 3< <(find . -print0)

read -d '' Lit les enregistrements délimités NUL au lieu des enregistrements délimités par des sauts de ligne.

bash-4.4 Et plus peuvent également stocker les fichiers renvoyés par find -print0 Dans un tableau avec:

readarray -td '' files < <(find . -print0)

L'équivalent de zsh (qui a l'avantage de conserver le statut de sortie de find):

files=(${(0)"$(find . -print0)"})

Avec zsh, vous pouvez traduire la plupart des expressions find en une combinaison de globings récursifs avec des qualificatifs glob. Par exemple, boucler sur find . -name '*.txt' -type f -mtime -1 Serait:

for file (./**/*.txt(ND.m-1)) cmd $file

Ou

for file (**/*.txt(ND.m-1)) cmd -- $file

(attention au besoin de -- comme avec **/*, les chemins de fichiers ne commencent pas par ./, donc peuvent commencer par - par exemple).

ksh93 Et bash ont finalement ajouté la prise en charge de **/ (Mais pas plus de formes avancées de globbing récursif), mais toujours pas les qualificatifs glob qui utilisent ** très limité là-bas. Sachez également que bash avant 4.3 suit les liens symboliques lors de la descente de l'arborescence des répertoires.

Comme pour boucler sur $(find .), cela signifie également stocker la liste complète des fichiers en mémoire1. Cela peut être souhaitable, mais dans certains cas, lorsque vous ne voulez pas que vos actions sur les fichiers aient une influence sur la recherche de fichiers (comme lorsque vous ajouter plus de fichiers qui pourraient se retrouver eux-mêmes).

Autres considérations de fiabilité/sécurité

Conditions de course

Maintenant, si nous parlons de fiabilité, nous devons mentionner les conditions de concurrence entre le moment où find/zsh trouve un fichier et vérifie qu'il répond aux critères et le temps qu'il est utilisé ( course TOCTO ).

Même lors de la descente d'une arborescence de répertoires, il faut s'assurer de ne pas suivre les liens symboliques et de le faire sans course TOCTOU. find (GNU find au moins) fait cela en ouvrant les répertoires en utilisant openat() avec les bons drapeaux O_NOFOLLOW (Là où c'est supporté) et en gardant un descripteur de fichier ouvert pour chaque répertoire, zsh/bash/ksh ne fait pas cela. Ainsi, face à un attaquant capable de remplacer un répertoire par un lien symbolique au bon moment, vous pourriez finir par descendre dans le mauvais répertoire.

Même si find descend le répertoire correctement, avec -exec cmd {} \; Et encore plus avec -exec cmd {} +, Une fois que cmd est exécuté, par exemple comme cmd ./foo/bar ou cmd ./foo/bar ./foo/bar/baz, au moment où cmd utilise ./foo/bar, les attributs de bar peuvent ne plus répondre aux critères correspondant à find, mais pire encore, ./foo peut avoir été remplacé par un lien symbolique vers un autre endroit (et la fenêtre de course est beaucoup plus grande avec -exec {} +find attend pour avoir suffisamment de fichiers pour appeler cmd).

Certaines implémentations find ont un prédicat [non standard pour l'instant] -execdir Pour atténuer le deuxième problème.

Avec:

find . -execdir cmd -- {} \;

findchdir() s dans le répertoire parent du fichier avant d'exécuter cmd. Au lieu d'appeler cmd -- ./foo/bar, Il appelle cmd -- ./bar (cmd -- bar Avec certaines implémentations, d'où le --), Donc le problème avec ./foo Étant changé en lien symbolique est évité. Cela rend l'utilisation de commandes comme rm plus sûre (cela pourrait toujours supprimer un fichier différent, mais pas un fichier dans un répertoire différent), mais pas des commandes qui peuvent modifier les fichiers à moins qu'elles n'aient été conçues pour ne pas suivre les liens symboliques.

-execdir cmd -- {} + Fonctionne parfois aussi mais avec plusieurs implémentations dont certaines versions de GNU find, il est équivalent à -execdir cmd -- {} \;.

-execdir A également l'avantage de contourner certains des problèmes associés aux arborescences de répertoires trop profondes.

Dans:

find . -exec cmd {} \;

la taille du chemin donné à cmd augmentera avec la profondeur du répertoire dans lequel se trouve le fichier. Si cette taille est supérieure à PATH_MAX (quelque chose comme 4k sous Linux), alors tout appel système que cmd fait sur ce chemin échouera avec une erreur ENAMETOOLONG.

Avec -execdir, Seul le nom du fichier (éventuellement préfixé avec ./) Est transmis à cmd. Les noms de fichiers eux-mêmes sur la plupart des systèmes de fichiers ont une limite beaucoup plus basse (NAME_MAX) Que PATH_MAX, Donc l'erreur ENAMETOOLONG est moins susceptible d'être rencontrée.

Octets vs caractères

En outre, souvent ignoré lors de l'examen de la sécurité autour de find et plus généralement avec la gestion des noms de fichiers en général, le fait que sur la plupart des systèmes de type Unix, les noms de fichiers sont des séquences d'octets (n'importe quelle valeur d'octet mais 0 dans un chemin de fichier , et sur la plupart des systèmes (ceux basés sur ASCII, nous ignorerons les rares basés sur EBCDIC pour l'instant) 0x2f est le délimiteur de chemin).

C'est aux applications de décider si elles veulent considérer ces octets comme du texte. Et ils le font généralement, mais généralement la traduction d'octets en caractères se fait en fonction des paramètres régionaux de l'utilisateur, en fonction de l'environnement.

Cela signifie qu'un nom de fichier donné peut avoir une représentation textuelle différente selon les paramètres régionaux. Par exemple, la séquence d'octets 63 f4 74 e9 2e 74 78 74 Serait côté.txt Pour une application interprétant ce nom de fichier dans un environnement local où le jeu de caractères est ISO-8859-1, et cєtщ.txt Dans un locale où le jeu de caractères est IS0-8859-5 à la place.

Pire. Dans un environnement où le jeu de caractères est UTF-8 (la norme de nos jours), 63 f4 74 e9 2e 74 78 74 ne peut tout simplement pas être mappé en caractères!

find est une de ces applications qui considère les noms de fichiers comme du texte pour ses prédicats -name/-path (Et plus, comme -iname Ou -regex avec certaines implémentations).

Cela signifie que, par exemple, avec plusieurs implémentations find (y compris GNU find)).

find . -name '*.txt'

ne trouverait pas notre fichier 63 f4 74 e9 2e 74 78 74 ci-dessus lorsqu'il est appelé dans un environnement local UTF-8 en tant que * (qui correspond à 0 ou plus caractères , pas d'octets) ne pouvait pas correspondre à ces non-caractères.

LC_ALL=C find... Contournerait le problème car les paramètres régionaux C impliquent un octet par caractère et (généralement) garantissent que toutes les valeurs d'octets sont mappées à un caractère (bien que celles éventuellement non définies pour certaines valeurs d'octets).

Maintenant, quand il s'agit de faire une boucle sur ces noms de fichiers à partir d'un shell, cet octet vs caractère peut également devenir un problème. Nous voyons généralement 4 principaux types d'obus à cet égard:

  1. Ceux qui ne sont toujours pas conscients de plusieurs octets comme dash. Pour eux, un octet correspond à un personnage. Par exemple, en UTF-8, côté Est de 4 caractères, mais 6 octets. Dans un environnement local où UTF-8 est le jeu de caractères, dans

    find . -name '????' -exec dash -c '
      name=${1##*/}; echo "${#name}"' sh {} \;
    

    find trouvera avec succès les fichiers dont le nom se compose de 4 caractères encodés en UTF-8, mais dash rapporterait des longueurs comprises entre 4 et 24.

  2. yash: le contraire. Il ne traite que des caractères . Toutes les entrées nécessaires sont traduites en interne en caractères. Cela rend le Shell le plus cohérent, mais cela signifie également qu'il ne peut pas faire face à des séquences d'octets arbitraires (celles qui ne se traduisent pas en caractères valides). Même dans les paramètres régionaux C, il ne peut pas gérer les valeurs d'octets supérieures à 0x7f.

    find . -exec yash -c 'echo "$1"' sh {} \;
    

    dans un environnement UTF-8 échouera sur notre ISO-8859-1 côté.txt de plus tôt par exemple.

  3. Ceux comme bash ou zsh où la prise en charge multi-octets a été progressivement ajoutée. Ceux-ci reviendront à considérer les octets qui ne peuvent pas être mappés sur des caractères comme s'ils étaient des caractères. Ils ont encore quelques bugs ici et là, en particulier avec les jeux de caractères multi-octets moins courants comme GBK ou BIG5-HKSCS (ceux-ci étant assez méchants car beaucoup de leurs caractères multi-octets contiennent des octets dans la plage 0-127 (comme le ASCII caractères)).

  4. Ceux comme sh de FreeBSD (11 au moins) ou mksh -o utf8-mode Qui prennent en charge plusieurs octets mais uniquement pour UTF-8.

Remarques

1 Par souci d'exhaustivité, nous pourrions mentionner un moyen hacky dans zsh pour parcourir les fichiers en utilisant un globing récursif sans stocker la liste entière en mémoire:

process() {
  something with $REPLY
  false
}
: **/*(ND.m-1+process)

+cmd Est un qualificatif global qui appelle cmd (généralement une fonction) avec le chemin de fichier actuel dans $REPLY. La fonction renvoie vrai ou faux pour décider si le fichier doit être sélectionné (et peut également modifier $REPLY Ou renvoyer plusieurs fichiers dans un tableau $reply). Ici, nous effectuons le traitement dans cette fonction et retournons false afin que le fichier ne soit pas sélectionné.

94

Pourquoi le bouclage sur la sortie de find est-il une mauvaise pratique?

La réponse simple est:

Parce que les noms de fichiers peuvent contenir n'importe quel caractère.

Par conséquent, il n'y a pas de caractère imprimable que vous pouvez utiliser de manière fiable pour délimiter les noms de fichiers.


Les sauts de ligne sont souvent utilisés (incorrectement) pour délimiter les noms de fichiers, car il est inhabituel d'inclure des caractères de retour à la ligne dans les noms de fichiers.

Cependant, si vous construisez votre logiciel autour d'hypothèses arbitraires, vous ne gérez tout simplement pas au mieux les cas inhabituels et, au pire, vous vous exposez à des exploits malveillants qui cèdent le contrôle de votre système. C'est donc une question de robustesse et de sécurité.

Si vous pouvez écrire des logiciels de deux manières différentes, et que l'un d'eux gère correctement les cas Edge (entrées inhabituelles), mais que l'autre est plus facile à lire, vous pourriez argumenter qu'il y a un compromis. (Je ne le ferais pas. Je préfère le bon code.)

Cependant, si la version correcte et robuste du code est aussi facile à lire, il n'y a aucune excuse pour écrire du code qui échoue sur les cas Edge. C'est le cas avec find et la nécessité d'exécuter une commande sur chaque fichier trouvé.


Soyons plus précis: sur un système UNIX ou Linux, les noms de fichiers peuvent contenir n'importe quel caractère à l'exception d'un / (qui est utilisé comme séparateur de composants de chemin), et ils ne peuvent pas contenir d'octet nul.

Un octet nul est donc le moyen uniquement de délimiter les noms de fichiers.


Puisque GNU find inclut un -print0 primaire qui utilisera un octet nul pour délimiter les noms de fichiers qu'il affiche, GNU find can à utiliser en toute sécurité avec GNU xargs et son -0 drapeau (et -r flag) pour gérer la sortie de find:

find ... -print0 | xargs -r0 ...

Cependant, il n'y a pas de bon raison pour utiliser ce formulaire, car:

  1. Il ajoute une dépendance à GNU findutils qui n'a pas besoin d'être là, et
  2. find est conçu pour pouvoir exécuter des commandes sur les fichiers qu'il trouve.

En outre, GNU xargs nécessite -0 et -r, alors que FreeBSD xargs ne nécessite que -0 (et n'a pas de -r option), et certains xargs ne prennent pas en charge -0 du tout. Il est donc préférable de s'en tenir aux fonctionnalités POSIX de find (voir la section suivante) et de sauter xargs.

Quant au point 2 —find est capable d'exécuter des commandes sur les fichiers qu'il trouve — je pense que Mike Loukides l'a mieux dit:

L'activité de find est d'évaluer des expressions - pas de localiser des fichiers. Oui, find localise certainement les fichiers; mais c'est vraiment juste un effet secondaire.

-- Unix Power Tools


tilisations spécifiées par POSIX de find

Quelle est la bonne façon d'exécuter une ou plusieurs commandes pour chacun des résultats de find?

Pour exécuter une seule commande pour chaque fichier trouvé, utilisez:

find dirname ... -exec somecommand {} \;

Pour exécuter plusieurs commandes en séquence pour chaque fichier trouvé, où la deuxième commande ne doit être exécutée que si la première commande réussit, utilisez:

find dirname ... -exec somecommand {} \; -exec someothercommand {} \;

Pour exécuter une seule commande sur plusieurs fichiers à la fois:

find dirname ... -exec somecommand {} +

find en combinaison avec sh

Si vous devez utiliser les fonctions Shell dans la commande, telles que la redirection de la sortie ou la suppression d'une extension du nom de fichier ou quelque chose de similaire, vous pouvez utiliser le sh -c construire. Vous devez savoir quelques choses à ce sujet:

  • Jamais incorporé {} directement dans le code sh. Cela permet l'exécution de code arbitraire à partir de noms de fichiers conçus de manière malveillante. De plus, il n'est même pas spécifié par POSIX que cela fonctionnera du tout. (Voir le point suivant.)

  • N'utilisez pas {} plusieurs fois, ou utilisez-le dans le cadre d'un argument plus long. Ce n'est pas portable. Par exemple, ne faites pas ceci:

    find ... -exec cp {} somedir/{}.bak \;

    Pour citer Spécifications POSIX pour find :

    Si une chaîne utilitaire_name ou argument contient les deux caractères "{}", mais pas seulement les deux caractères "{}", il est défini par l'implémentation si find remplace ces deux caractères ou utilise la chaîne sans changement.

    ... Si plusieurs arguments contenant les deux caractères "{}" sont présents, le comportement n'est pas spécifié.

  • Les arguments suivant la chaîne de commande Shell passés à -c sont définies sur les paramètres positionnels du Shell, commençant par $0. Ne commençant pas par $1.

    Pour cette raison, il est bon d'inclure un "mannequin" $0 valeur, telle que find-sh, qui sera utilisé pour le rapport d'erreurs à partir du shell généré. Cela permet également d'utiliser des constructions telles que "$@" lors de la transmission de plusieurs fichiers au shell, tout en omettant une valeur pour $0 signifierait que le premier fichier transmis serait défini sur $0 et donc non inclus dans "$@".


Pour exécuter une seule commande Shell par fichier, utilisez:

find dirname ... -exec sh -c 'somecommandwith "$1"' find-sh {} \;

Cependant, cela donnera généralement de meilleures performances pour gérer les fichiers dans une boucle Shell afin de ne pas générer de shell pour chaque fichier trouvé:

find dirname ... -exec sh -c 'for f do somecommandwith "$f"; done' find-sh {} +

(Notez que for f do est équivalent à for f in "$@"; do et gère tour à tour chacun des paramètres de position. En d'autres termes, il utilise chacun des fichiers trouvés par find, quels que soient les caractères spéciaux dans leurs noms.)


Autres exemples d'utilisation correcte de find:

(Remarque: n'hésitez pas à étendre cette liste.)

185
Wildcard

Cette réponse concerne les jeux de résultats très volumineux et concerne principalement les performances, par exemple lors de l'obtention d'une liste de fichiers sur un réseau lent. Pour de petites quantités de fichiers (disons quelques 100 ou peut-être même 1000 sur un disque local), la plupart c'est sans objet.

Parallélisme et utilisation de la mémoire

Mis à part les autres réponses données, liées aux problèmes de séparation et autres, il y a un autre problème avec

for file in `find . -type f -name ...`; do smth with ${file}; done

La partie à l'intérieur des backticks doit être évaluée en premier avant d'être divisée sur les sauts de ligne. Cela signifie que si vous obtenez une énorme quantité de fichiers, il peut s'étouffer sur les limites de taille des différents composants; vous pouvez manquer de mémoire s'il n'y a pas de limites; et dans tous les cas, vous devez attendre que la liste entière soit sortie par find puis analysée par for avant même d'exécuter votre premier smth.

La méthode Unix préférée consiste à travailler avec des canaux, qui fonctionnent de manière inhérente en parallèle, et qui n'ont pas non plus besoin de tampons arbitrairement énormes en général. Cela signifie que vous préféreriez de beaucoup que le find s'exécute en parallèle de votre smth, et que vous ne conserviez le nom de fichier actuel que dans RAM pendant qu'il remet off sur smth.

Une solution au moins en partie OKish pour cela est find -exec smth. Il supprime la nécessité de conserver tous les noms de fichiers en mémoire et fonctionne bien en parallèle. Malheureusement, il démarre également un processus smth par fichier. Si smth ne peut fonctionner que sur un seul fichier, il doit en être ainsi.

Si possible, la solution optimale serait find -print0 | smth, smth pouvant traiter les noms de fichiers sur son STDIN. Vous n'avez alors qu'un seul processus smth, quel que soit le nombre de fichiers, et vous n'avez besoin de mettre en mémoire tampon qu'une petite quantité d'octets (quelle que soit la mise en mémoire tampon de canal intrinsèque en cours) entre les deux processus. Bien sûr, cela est plutôt irréaliste si smth est une commande Unix/POSIX standard, mais peut être une approche si vous l'écrivez vous-même.

Si ce n'est pas possible, alors find -print0 | xargs -0 smth est, probablement, l'une des meilleures solutions. Comme @ dave_thompson_085 mentionné dans les commentaires, xargs répartit les arguments sur plusieurs exécutions de smth lorsque les limites du système sont atteintes (par défaut, dans la plage de 128 Ko ou quelle que soit la limite imposée par exec sur le système), et dispose d'options pour influencer le nombre de fichiers attribués à un appel de smth, trouvant ainsi un équilibre entre le nombre de processus smth et le délai initial.

EDIT: a supprimé les notions de "meilleur" - il est difficile de dire si quelque chose de mieux surgira. ;)

11
AnoE

L'une des raisons est que les espaces blancs jettent une clé dans les travaux, ce qui fait que le fichier 'foo bar' est évalué comme 'foo' et 'bar'.

$ ls -l
-rw-rw-r-- 1 ec2-user ec2-user 0 Nov  7 18:24 foo bar
$ for file in `find . -type f` ; do echo filename $file ; done
filename ./foo
filename bar
$

Fonctionne bien si -exec est utilisé à la place

$ find . -type f -exec echo filename {} \;
filename ./foo bar
$ find . -type f -exec stat {} \;
  File: ‘./foo bar’
  Size: 0               Blocks: 0          IO Block: 4096   regular empty file
Device: ca01h/51713d    Inode: 9109        Links: 1
Access: (0664/-rw-rw-r--)  Uid: (  500/ec2-user)   Gid: (  500/ec2-user)
Access: 2016-11-07 18:24:42.027554752 +0000
Modify: 2016-11-07 18:24:42.027554752 +0000
Change: 2016-11-07 18:24:42.027554752 +0000
 Birth: -
$
4
steve

Parce que la sortie de toute commande est une chaîne unique, mais votre boucle a besoin d'un tableau de chaînes pour boucler. La raison pour laquelle cela "fonctionne" est que les coquilles divisent trahison la chaîne sur les espaces blancs pour vous.

Deuxièmement, à moins que vous n'ayez besoin d'une fonctionnalité particulière de find, sachez que votre Shell peut très probablement déjà développer un modèle glob récursif tout seul, et surtout, qu'il se développera dans un tableau approprié.

Exemple bash:

shopt -s nullglob globstar
for i in **
do
    echo «"$i"»
done

Même chose pour le poisson:

for i in **
    echo «$i»
end

Si vous avez besoin des fonctionnalités de find, assurez-vous de ne fractionner que sur NUL (comme le find -print0 | xargs -r0 idiome).

Les poissons peuvent parcourir la sortie délimitée NUL. Donc celui-ci est en fait pas mauvais:

find -print0 | while read -z i
    echo «$i»
end

Comme dernier petit problème, dans de nombreux shells (pas Fish bien sûr), le bouclage sur la sortie de la commande fera du corps de la boucle un sous-shell (ce qui signifie que vous ne pouvez pas définir une variable de quelque manière que ce soit visible après la boucle se termine), ce qui n'est jamais ce que vous voulez.

2
user2394284

Faire une boucle sur la sortie de find n'est pas une mauvaise pratique - ce qui est une mauvaise pratique (dans cette situation et toutes les situations) est en supposant votre entrée est un format particulier au lieu de sachant (tester et confirmer) c'est un format particulier.

tldr/cbf: find | parallel stuff

1
Jan Kyu Peblik

Quelle est la bonne façon d'exécuter une ou plusieurs commandes pour chaque nom/chemin de fichier renvoyé par find?

Je suis sur un Mac utilisant zsh. J'ai trouvé cette question en cherchant comment obtenir les résultats de la commande fd (canalisation vers fzf) dans un tableau. Plus précisément, d'une manière que je n'ai pas eu à me soucier que les espaces dans les noms de fichiers soient stockés en tant qu'éléments séparés. Avec ce tableau, j'envoie ensuite ces noms de fichiers à un autre script, un à la fois.

J'ai essayé de voter pour la réponse de Stéphane parce qu'elle m'a donné les détails dont j'avais besoin pour trouver ma réponse - qui pour moi était d'utiliser ceci:

array_name=(${(0)"$(fd "\.tar\.gz$|\.tgz$|\.Zip$|\.tar.bz2|\.tar$" | fzf --print0)"})

Je devrais probablement mieux comprendre ce que fait la pièce ($ {(0) ...}). C'est peut-être lié au fait de pouvoir utiliser IFS=$'\0'. J'ai essayé de faire une recherche rapide et ce que j'ai pu trouver concernait le\0. J'ai trouvé cela ici: dans la documentation zsh sur l'expansion , où il mentionne:

14.3.1 Indicateurs d'extension de paramètre

Si l'accolade ouvrante est directement suivie d'une parenthèse ouvrante, la chaîne jusqu'à la parenthèse fermante correspondante sera considérée comme une liste d'indicateurs. Dans les cas où la répétition d'un indicateur est significative, les répétitions n'ont pas besoin d'être consécutives; par exemple, '(q%q%q) "Signifie la même chose que le plus lisible" (%%qqq) ’. Les indicateurs suivants sont pris en charge:

[...]
0
Fractionner le résultat de l'expansion sur des octets nuls. Ceci est un raccourci pour "ps:\0: ’.

Pourquoi le bouclage sur la sortie de find est-il une mauvaise pratique?

Je suis la dernière personne à essayer de vous donner des conseils à ce sujet même après quelques années de script bash. :) Mais ce que j'ai essayé de faire moins, c'est de mettre le rc d'une commande dans une variable, puis de tester la variable. Au lieu de cela, j'essaie d'utiliser ces types d'instructions if:

if echo “${something}” | grep '^s’ -q; then {
:
} Elif [[ $(dirname \""${file}\"") == "." ]]; then {

J'espère que cela vous aide/aide quelqu'un.

0
Gregg