Comment passer des fichiers trouvés par find comme arguments?


9

D'abord pour couper les réponses triviales mais inapplicables: je ne peux utiliser ni l' astuce find+ xargsni ses variantes (comme findavec -exec) car j'ai besoin d'utiliser peu de telles expressions par appel. J'y reviendrai à la fin.


Maintenant, pour un meilleur exemple, considérons:

$ find -L some/dir -name \*.abc | sort
some/dir/1.abc
some/dir/2.abc
some/dir/a space.abc

Comment puis-je transmettre ces arguments à program?

Le faire ne fait pas l'affaire

$ ./program $(find -L some/dir -name \*.abc | sort)

échoue car programobtient les arguments suivants:

[0]: ./program
[1]: some/dir/1.abc
[2]: some/dir/2.abc
[3]: some/dir/a
[4]: space.abc

Comme on peut le voir, le chemin avec l'espace a été divisé et programconsidère qu'il s'agit de deux arguments différents.

Citez jusqu'à ce que cela fonctionne

Il semble que les utilisateurs novices comme moi, lorsqu'ils sont confrontés à de tels problèmes, ont tendance à ajouter des citations au hasard jusqu'à ce que cela fonctionne enfin - seulement ici, cela ne semble pas aider ...

"$(…)"

$ ./program "$(find -L some/dir -name \*.abc | sort)"
[0]: ./program
[1]: some/dir/1.abc
some/dir/2.abc
some/dir/a space.abc

Étant donné que les guillemets empêchent la séparation des mots, tous les fichiers sont transmis en tant qu'argument unique.

Citation de chemins individuels

Une approche prometteuse:

$ ./program $(find -L some/dir -name \*.abc -printf '"%p"\n' | sort)
[1]: "some/dir/1.abc"
[2]: "some/dir/2.abc"
[3]: "some/dir/a
[4]: space.abc"

Les citations sont là, bien sûr. Mais ils ne sont plus interprétés. Ils ne sont qu'une partie des chaînes. Ainsi, non seulement ils n'ont pas empêché le fractionnement de mots, mais ils se sont aussi mis à discuter!

Changer IFS

Ensuite, j'ai essayé de jouer avec IFS. Je préférerais findavec -print0et sortavec de -ztoute façon - afin qu'ils n'aient aucun problème sur les "chemins câblés" eux-mêmes. Alors pourquoi ne pas forcer le mot à se scinder sur le nullpersonnage et tout avoir?

$ ./program $(IFS=$'\0' find -L some/dir -name \*.abc -print0 | sort -z)
[0]: ./program
[1]: some/dir/1.abcsome/dir/2.abcsome/dir/a
[2]: space.abc

Donc, il se divise toujours sur l'espace et ne se divise pas sur le null.

J'ai essayé de placer l' IFSaffectation à la fois $(…)(comme indiqué ci-dessus) et avant ./program. Aussi j'essayé d' autres syntaxe comme \0, \x0, à la \x00fois cité avec 'et "ainsi qu'avec et sans $. Rien de tout cela ne semblait faire de différence…


Et là, je suis à court d'idées. J'ai essayé quelques autres choses, mais tout semblait se résumer aux mêmes problèmes que ceux énumérés.

Que pouvais-je faire d'autre? Est-ce faisable du tout?

Bien sûr, je pourrais faire programaccepter les modèles et faire des recherches lui-même. Mais c'est beaucoup de travail double tout en le fixant à une syntaxe spécifique. (Qu'en est-il de la fourniture de fichiers par un greppar exemple?).

Je pourrais aussi faire programaccepter un fichier avec une liste de chemins. Ensuite, je peux facilement vider l' findexpression dans un fichier temporaire et fournir le chemin d'accès à ce fichier uniquement. Cela pourrait être pris en charge le long de chemins directs de sorte que si l'utilisateur a juste un chemin simple, il peut être fourni sans fichier intermédiaire. Mais cela ne semble pas agréable - il faut créer des fichiers supplémentaires et en prendre soin, sans parler de l'implémentation supplémentaire requise. (Du côté positif, cependant, cela pourrait être un sauvetage pour les cas où le nombre de fichiers en tant qu'arguments commence à causer des problèmes avec la longueur de la ligne de commande…)


À la fin, permettez-moi de vous rappeler à nouveau que les astuces find+ xargs(et similaires) ne fonctionneront pas dans mon cas. Pour simplifier la description, je ne montre qu'un seul argument. Mais mon vrai cas ressemble plus à ceci:

$ ABC_FILES=$(find -L some/dir -name \*.abc | sort)
$ XYZ_FILES=$(find -L other/dir -name \*.xyz | sort)
$ ./program --abc-files $ABC_FILES --xyz-files $XYZ_FILES

Donc, faire une xargsrecherche à partir d'une recherche me laisse encore comment gérer l'autre…

Réponses:


13

Utilisez des tableaux.

Si vous n'avez pas besoin de gérer la possibilité de nouvelles lignes dans vos noms de fichiers, vous pouvez

mapfile -t ABC_FILES < <(find -L some/dir -name \*.abc | sort)
mapfile -t XYZ_FILES < <(find -L other/dir -name \*.xyz | sort)

puis

./program --abc-files "${ABC_FILES[@]}" --xyz-files "${XYZ_FILES[@]}"

Si vous ne Devez gérer dans les nouvelles lignes des noms de fichiers, et ont bash> = 4.4, vous pouvez utiliser -print0et -d ''à nul mettre fin aux noms pendant la construction du tableau:

mapfile -td '' ABC_FILES < <(find -L some/dir -name \*.abc -print0 | sort -z)

(et de même pour le XYZ_FILES). Si vous n'avez pas la nouvelle bash, alors vous pouvez utiliser une boucle de lecture terminée par null pour ajouter des noms de fichiers aux tableaux, par exemple

ABC_FILES=()
while IFS= read -rd '' f; do ABC_FILES+=( "$f" ); done < <(find -L some/dir -name \*.abc -print0 | sort -z)

Excellent! Je pensais aux tableaux. Mais d'une manière ou d'une autre, je n'ai rien trouvé là-dessus mapfile(ou son synonyme readarray). Mais, il fonctionne!
Adam Badura

Pourtant, vous pourriez l'améliorer un peu. La version Bash <4.4 (que j'ai par hasard ...) avec une whileboucle ne vide pas le tableau. Ce qui signifie que si aucun fichier n'est trouvé, le tableau n'est pas défini. Alors que s'il est déjà défini, de nouveaux fichiers seront ajoutés (au lieu de remplacer les anciens). Il semble que l'ajout declare -a ABC_FILES='()';avant whilefasse l'affaire. (Tout en ajoutant simplement ABC_FILES='()';non.)
Adam Badura

Aussi qu'est-ce que cela < <signifie ici? Est-ce la même chose que <<? Je ne pense pas que le changer pour <<produire une erreur de syntaxe ("jeton inattendu` ('"). Alors qu'est-ce que c'est et comment ça marche?
Adam Badura

Une autre amélioration (parallèlement à mon utilisation particulière) consiste à construire un autre tableau. Nous les avons donc ABC_FILES. C'est bon. Mais il est utile de faire également ABS_ARGSqui est un tableau vide si ABC_FILESest vide ou bien c'est un tableau ('--abc-files' "${ABC_FILES[@]}"). De cette façon, plus tard, je peux l'utiliser comme ceci: ./program "${ABC_ARGS[@]}" "${XYZ_ARGS[@]}"et assurez-vous qu'il fonctionnera correctement quel que soit le groupe (le cas échéant) vide. Ou pour le dire différemment: cette façon --abc-files(et --xyz-files) ne sera fournie que si elle est suivie par un chemin réel.
Adam Badura

1
@AdamBadura: while read ... done < <(find blah)est une redirection <de shell normale à partir d'un fichier spécial créé par PROCESS SUBSTITUTION . Cela diffère de la tuyauterie find blah | while read ... donecar le pipeline exécute la whileboucle dans un sous-shell, de sorte que les variables définies ne sont pas conservées pour les commandes suivantes.
dave_thompson_085

3

Vous pouvez utiliser IFS = newline (en supposant qu'aucun nom de fichier ne contient newline) mais vous devez le définir dans le shell externe AVANT la substitution:

$ ls -1
a file with spaces
able
alpha
baker
boo hoo hoo
bravo
$ # note semicolon here; it's not enough to be in the environment passed
$ # to printf, it must be in the environment OF THE SHELL WHILE PARSING
$ IFS=$'\n'; printf '%s\n' --afiles $(find . -name 'a*') --bfiles $(find . -name 'b*')
--afiles
./able
./a file with spaces
./alpha
--bfiles
./bravo
./boo hoo hoo
./baker

Avec zshmais pas, bashvous pouvez également utiliser null $'\0'. Même en bashvous pourriez gérer la nouvelle ligne s'il y a un caractère suffisamment étrange qui n'est jamais utilisé comme

 IFS=$'\1'; ... $(find ... -print0 | tr '\0' '\1') ...

Cependant, cette approche ne gère pas la demande supplémentaire que vous avez faite dans les commentaires sur la réponse de @ steeldriver pour omettre les --afiles si find a est vide.


Donc, si je comprends bien dans Bash, il n'y a aucun moyen de forcer la IFSséparation null?
Adam Badura

@AdamBadura: Je suis sûr que non; bash n'autorise l'octet nul dans aucune variable, y compris IFS. Notez que la read -d ''méthode utilisée dans les méthodes de Steeldriver est une chaîne vide et non une chaîne contenant un octet nul. (Et une option de commande n'est pas une var en tant que telle de toute façon.)
dave_thompson_085

Vous devez également désactiver globbing ( set -o noglob) avant d'utiliser cet opérateur split + glob (sauf dans zsh).
Stéphane Chazelas


@AdamBadura Oui, en bash, un null est exactement le même que $'\0'et aussi que ''.
Isaac

1

Je ne suis pas sûr de comprendre pourquoi vous avez abandonné xargs.

Donc, faire une xargsrecherche à partir d'une recherche me laisse encore comment gérer l'autre…

La chaîne --xyz-filesn'est qu'un des nombreux arguments et il n'y a aucune raison de la considérer comme spéciale avant qu'elle ne soit interprétée par votre programme. Je pense que vous pouvez le passer xargsparmi les deux findrésultats:

{ find -L some/dir -name \*.abc -print0 | sort -z; echo -ne "--xyz-files\0"; find -L other/dir -name \*.xyz -print0 | sort -z; } | xargs -0 ./program --abc-files

Tu as raison! Cela fonctionne aussi! Notez cependant que vous avez raté la -print0seconde find. De plus, si je procède de cette façon, je mettrais --abc-filesaussi echole - juste pour la cohérence.
Adam Badura

Cette approche semble plus simple et un peu plus unilatérale que l'approche par tableau. Cependant, cela nécessiterait une logique supplémentaire pour couvrir le cas où s'il n'y a pas de .abcfichiers, il ne devrait pas y en avoir --abc-files(comme avec .xyz). La solution basée sur un tableau par Steeldriver nécessite également une logique supplémentaire, mais cette logique est triviale là-bas alors qu'elle pourrait ne pas être si triviale ici, détruisant le principal avantage de cette solution - la simplicité.
Adam Badura du

De plus, je ne suis pas vraiment sûr, mais je suppose qu'il xargsn'essaiera jamais de fractionner les arguments et de créer quelques commandes au lieu d'une, à moins qu'il ne soit explicitement chargé de le faire avec les arguments -L, --max-lines( -l), --max-args( -n) ou --max-chars( -s). Ai-je raison? Ou y a-t-il des défauts? Comme mon programme ne gérerait pas une telle division correctement et j'aurais préféré ne pas l'appeler ...
Adam Badura

1
@AdamBadura Missing -print0- corrigé, merci. Je ne connais pas toutes les réponses mais je suis d'accord que ma solution rend difficile l'inclusion d'une logique supplémentaire. J'irais probablement avec des tableaux moi-même, maintenant quand je connais cette approche. Ma réponse n'était pas vraiment pour vous. Vous aviez déjà accepté l'autre réponse et j'ai supposé que votre problème était résolu. Je voulais juste souligner que vous pouvez transmettre des arguments de plusieurs sources xargs, ce qui n'était pas évident à première vue. Vous pouvez le considérer comme une preuve de concept. Maintenant, nous connaissons tous peu d'approches différentes et nous pouvons consciemment choisir ce qui nous convient dans chaque cas particulier.
Kamil Maciorowski

Oui, j'ai déjà implémenté la solution basée sur la baie et cela fonctionne comme un charme. Je suis particulièrement fier de la propreté avec laquelle il traite l'option (si aucun fichier, alors non --abc-files). Mais vous avez raison - il est bon de connaître vos alternatives! Surtout que je pensais à tort que ce n'était pas possible.
Adam Badura
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.