J'essaie d'invoquer un script avec une liste de noms de fichiers collectés par find
. Rien de spécial, juste quelque chose comme ça:
$ myscript `find . -name something.txt`
Le problème est que certains des chemins d'accès contiennent des espaces, ils sont donc divisés en deux noms invalides lors de l'expansion des arguments. Normalement, j'entoure les noms de guillemets, mais ici, ils sont insérés par l'expansion des guillemets. J'ai essayé de filtrer la sortie find
et d'entourer chaque nom de fichier avec des guillemets, mais au moment où bash les voit, il est trop tard pour les supprimer et ils sont traités comme faisant partie du nom de fichier:
$ myscript `find . -name something.txt | sed 's/.*/"&"/'`
No such file or directory: '"./somedir/something.txt"'
Oui, ce sont les règles de traitement de la ligne de commande, mais comment la contourner?
C'est embarrassant mais je n'arrive pas à trouver la bonne approche. J'ai finalement compris comment le faire avec xargs -0 -n 10000
... mais c'est un hack si laid que je veux toujours demander: comment puis-je citer les résultats de l'expansion des guillemets, ou obtenir le même effet d'une autre manière?
Éditer: J'étais confus sur le fait que le xargs
fait Collect tous les arguments dans une liste unique argument, à moins qu'il ne dit autrement , ou peut - être dépassé les limites du système. Merci à tous de m'avoir remis au clair! D'autres, gardez cela à l'esprit lorsque vous lisez la réponse acceptée car elle n'est pas indiquée très directement.
J'ai accepté la réponse, mais ma question demeure: n'y a-t-il pas un moyen de protéger les espaces en expansion (ou en backtick $(...)
)? (Notez que la solution acceptée est une réponse non bash).
IFS="
, la nouvelle ligne,"
). Mais est-il nécessaire d'exécuter le script sur tous les noms de fichiers? Sinon, envisagez d'utiliser find it pour exécuter le script de chaque fichier.Réponses:
Vous pouvez effectuer les opérations suivantes en utilisant certaines implémentations de
find
etxargs
comme ceci.ou, normalement, juste
find
:Exemple
Disons que j'ai l'exemple de répertoire suivant.
Maintenant, disons que je l'ai pour
./myscript
.Maintenant, lorsque j'exécute la commande suivante.
Ou quand j'utilise le 2ème formulaire comme ceci:
Détails
trouver + xargs
Les 2 méthodes ci-dessus, bien que différentes, sont essentiellement les mêmes. La première consiste à prendre la sortie de find, à la diviser en utilisant NULLs (
\0
) via le-print0
commutateur pour trouver. Lexargs -0
est spécialement conçu pour accepter une entrée divisée à l'aide de valeurs NULL. Cette syntaxe non standard a été introduite par GNUfind
etxargs
se retrouve également de nos jours dans quelques autres comme les BSD les plus récents. L'-r
option est requise pour éviter d'appelermyscript
sifind
ne trouve rien avec GNUfind
mais pas avec BSD.REMARQUE: toute cette approche repose sur le fait que vous ne passerez jamais une chaîne excessivement longue. Si c'est le cas, alors une 2e invocation de
./myscript
sera lancée avec le reste des résultats ultérieurs de find.trouver avec +
C'est la méthode standard (bien qu'elle n'ait été ajoutée que relativement récemment (2005) à la mise en œuvre de GNU
find
). La capacité de faire ce que nous faisonsxargs
est littéralement intégréefind
. Ainsifind
, vous trouverez une liste de fichiers, puis passerez cette liste autant d'arguments que possible à la commande spécifiée après-exec
(notez que{}
cela ne peut être que juste avant+
dans ce cas), en exécutant les commandes plusieurs fois si nécessaire.Pourquoi ne pas citer?
Dans le premier exemple, nous prenons un raccourci en évitant complètement les problèmes de citation, en utilisant des valeurs NULL pour séparer les arguments. Quand
xargs
est donnée cette liste, il est chargé de se diviser sur les NULLs protégeant efficacement nos atomes de commande individuels.Dans le deuxième exemple, nous gardons les résultats internes à
find
et il sait donc à quoi correspond chaque atome de fichier, et garantira de les traiter de manière appropriée, évitant ainsi le problème de les citer.Taille maximale de la ligne de commande?
Cette question revient de temps en temps, donc en prime, je l'ajoute à cette réponse, principalement pour que je puisse la trouver à l'avenir. Vous pouvez utiliser
xargs
pour voir à quoi ressemble la limite de l'environnement:la source
+
argumentfind
(et vous l'utilisez aussi+
en prose, donc j'ai raté votre explication la première fois). Mais plus précisément, j'aurais mal compris ce quexargs
fait par défaut !!! En trois décennies d'utilisation d'Unix, je n'en ai jamais utilisé jusqu'à présent, mais je pensais que je connaissais ma boîte à outils ...xargs
est un diable d'une commande. Vous devez le lire et consultezfind
les pages de manuel plusieurs fois pour voir ce qu'ils peuvent faire. Les commutateurs peuvent être contre-positifs les uns des autres, ce qui ajoute à la confusion.$(..)
maintenant à la place. Il gère automatiquement l'imbrication des guillemets, etc. Les backticks sont obsolètes.Dans ce qui précède,
find
recherche tous les noms de fichiers correspondants et les fournit comme argumentsmyscript
. Cela fonctionne avec les noms de fichiers quels que soient les espaces ou tout autre caractère impair.Si tous les noms de fichiers tiennent sur une seule ligne, alors myscript est exécuté une fois. Si la liste est trop longue pour être manipulée par le shell, alors find exécutera myscript plusieurs fois si nécessaire.
PLUS: Combien de fichiers tiennent sur une ligne de commande?
man find
dit que lefind
construit en ligne de commande "de la même manière que xargs le construit". Etman xargs
que les limites dépendent du système et que vous pouvez les déterminer en exécutantxargs --show-limits
. (getconf ARG_MAX
est également une possibilité). Sous Linux, la limite est généralement (mais pas toujours) d' environ 2 millions de caractères par ligne de commande.la source
Quelques ajouts à la bonne réponse de @ slm.
La limitation de la taille des arguments se trouve sur l'
execve(2)
appel système (en fait, c'est sur la taille cumulée des chaînes d'argument et d'environnement et des pointeurs). Simyscript
est écrit dans un langage que votre shell peut interpréter, alors peut-être n'avez-vous pas besoin de l' exécuter , vous pourriez avoir votre shell simplement l'interpréter sans avoir à exécuter un autre interpréteur.Si vous exécutez le script en tant que:
C'est comme:
Sauf qu'il est interprété par un enfant du shell actuel, au lieu de l' exécuter (ce qui implique éventuellement l' exécution
sh
(ou ce que la ligne she-bang spécifie le cas échéant) avec encore plus d'arguments).Maintenant, évidemment, vous ne pouvez pas utiliser
find -exec {} +
la.
commande, car.
étant une commande intégrée du shell, elle doit être exécutée par le shell, pas parfind
.Avec
zsh
, c'est simple:Ou:
Bien qu'avec
zsh
, vous n'auriez pas besoinfind
en premier lieu car la plupart de ses fonctionnalités sont intégrées dans lezsh
globbing.bash
Cependant, les variables ne peuvent pas contenir de caractères NUL, vous devez donc trouver une autre façon. Une façon pourrait être:Vous pouvez également utiliser le remplacement récursif de style zsh avec l'
globstar
optionbash
4.0 et les versions ultérieures:Notez que les
**
liens symboliques suivis vers les répertoires jusqu'à ce qu'il soit corrigé dansbash
4.3. Notez également quebash
ne pas mettre en œuvre deszsh
qualificatifs jokers afin que vous ne serez pas toutes les fonctionnalités defind
là.Une autre alternative serait d'utiliser GNU
ls
:Les méthodes ci-dessus peuvent également être utilisées si vous voulez vous assurer qu'il
myscript
n'est exécuté qu'une seule fois (échec si la liste d'arguments est trop grande). Sur les versions récentes de Linux, vous pouvez augmenter et même lever cette limitation sur la liste des arguments avec:(Taille de pile 1GiB, dont un quart peut être utilisé pour la liste arg + env).
(sans limites)
la source
Dans la plupart des systèmes, il y a une limite à la longueur d'une ligne de commande passée à n'importe quel programme, en utilisant
xargs
ou-exec command {} +
. Deman find
:Les invocations seront beaucoup moins, mais ne seront pas garanties d'être une. Ce que vous devez faire est de lire les noms de fichiers séparés par NUL dans le script depuis stdin, possible en fonction d'un argument de ligne de commande
-o -
. Je ferais quelque chose comme:et implémentez les arguments d'option en
myscript
conséquence.la source
xargs
fonctionnement. Votre solution est en effet la plus robuste, mais elle est exagérée dans ce cas.Non, il n'y en a pas. Pourquoi donc?
Bash n'a aucun moyen de savoir ce qui doit être protégé et ce qui ne devrait pas l'être.
Il n'y a pas de tableaux dans le fichier / pipe Unix. Ce n'est qu'un flux d'octets. La commande à l'intérieur de
``
ou$()
génère un flux, que bash avale et traite comme une chaîne unique. À ce stade, vous n'avez que deux choix: le mettre entre guillemets, pour le conserver sous forme de chaîne, ou le mettre nu, afin que bash le divise en fonction de son comportement configuré.Donc, ce que vous devez faire si vous voulez un tableau est de définir un format d'octet qui a un tableau, et c'est ce que les outils aiment
xargs
etfind
font: si vous les exécutez avec l'-0
argument, ils fonctionnent selon un format de tableau binaire qui termine les éléments avec l'octet nul, ajoutant la sémantique au flux d'octets autrement opaque.Malheureusement,
bash
ne peut pas être configuré pour fractionner les chaînes sur l'octet nul. Merci à /unix//a/110108/17980 de nous avoir montré quezsh
.xargs
Vous voulez que votre commande s'exécute une fois, et vous avez dit que cela
xargs -0 -n 10000
résout votre problème. Ce n'est pas le cas, cela garantit que si vous avez plus de 10000 paramètres, votre commande s'exécutera plus d'une fois.Si vous voulez qu'il soit strictement exécuté une fois ou échoue, vous devez fournir l'
-x
argument et un-n
argument plus grand que l'-s
argument (vraiment: suffisamment grand pour qu'un tas d'arguments de longueur nulle plus le nom de la commande ne tiennent pas la-s
taille). ( man xargs , voir extrait ci-dessous)Le système sur lequel je suis actuellement a une pile limitée à environ 8M, voici donc ma limite:
frapper
Si vous ne voulez pas impliquer une commande externe, la boucle en lecture-lecture alimentant un tableau, comme indiqué dans /unix//a/110108/17980 , est le seul moyen pour bash de diviser les choses à l'octet nul.
L'idée de se procurer le script
( . ... "$@" )
pour éviter la limite de taille de pile est cool (je l'ai essayé, ça marche!), Mais probablement pas important dans des situations normales.L'utilisation d'un fd spécial pour le tube de processus est importante si vous voulez lire autre chose depuis stdin, mais sinon vous n'en aurez pas besoin.
Ainsi, la façon la plus simple "native", pour les besoins quotidiens des ménages:
Si vous aimez que votre arbre de processus soit propre et agréable à regarder, cette méthode vous permet de le faire
exec mynonscript "${files[@]}"
, ce qui supprime le processus bash de la mémoire, en le remplaçant par la commande appelée.xargs
restera toujours en mémoire pendant l'exécution de la commande appelée, même si la commande ne s'exécute qu'une seule fois.Ce qui parle contre la méthode bash native est la suivante:
bash n'est pas optimisé pour la gestion des tableaux.
homme xargs :
la source
ls "what is this"
rapportls `echo '"what is this"'`
. Quelqu'un a négligé d'implémenter le traitement des devis pour le résultat des backquotes.$(...)
) l'expansion?", Il semble donc approprié d'ignorer le traitement qui n'est pas fait dans cette situation.bash
ne le supporte pas nativement comme apparemmentzsh
.printf "%s\0"
etxargs -0
pour contourner une situation de citation où un outil intermédiaire passerait des paramètres à travers une chaîne analysée par un shell. Citer revient toujours pour vous mordre.