J'essaie d'invoquer un script avec une liste de noms de fichiers collectés par find
. Rien de spécial, tout simplement comme ça:
$ myscript `find . -name something.txt`
Le problème est que certains des noms de chemin contiennent des espaces, ils sont donc divisés en deux noms non valides sur l'expansion des arguments. Normalement, j'éournerais les noms avec des citations, mais ici, ils sont insérés par l'expansion du backquote. J'ai essayé de filtrer la sortie de find
et d'entourer chaque nom de fichier avec des citations, mais au moment où Bash les voit, il est trop tard pour les dépouiller 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 la manière dont la ligne de commande est traitée, mais comment puis-je vous entendre?
C'est embarrassant mais je ne parviens pas à proposer la bonne approche. J'ai finalement compris comment le faire avec xargs -0 -n 10000
... Mais c'est un piratage tellement laid que je veux toujours demander: comment puis-je citer les résultats de l'expansion de BackQuote ou atteindre le même effet d'une autre manière?
éditer : J'étais confus au sujet du fait que xargs
fait collecte tous les arguments dans une seule liste d'arguments, à moins que cela ne soit dit autrement ou que des limites du système puissent être dépassés. Merci à tout le monde pour me donner droit! D'autres, gardez cela à l'esprit lorsque vous lisez la réponse acceptée car elle n'est pas signalée très directement.
J'ai accepté la réponse, mais ma question reste: n'est-ce pas un moyen de protéger les espaces en backtktic (ou $(...)
) expansion? (Notez que la solution acceptée est une réponse non bash).
Vous pouvez effectuer les éléments suivants en utilisant certaines implémentations de find
et xargs
comme celui-ci.
$ find . -type f -print0 | xargs -r0 ./myscript
ou, standardement, juste find
:
$ find . -type f -exec ./myscript {} +
Dis que j'ai l'exemple de répertoire suivant.
$ tree
.
|-- dir1
| `-- a\ file1.txt
|-- dir2
| `-- a\ file2.txt
|-- dir3
| `-- a\ file3.txt
`-- myscript
3 directories, 4 files
Disons maintenant que j'ai cela pour ./myscript
.
#!/bin/bash
for i in "$@"; do
echo "file: $i"
done
Maintenant, quand j'exécute la commande suivante.
$ find . -type f -print0 | xargs -r0 ./myscript
file: ./dir2/a file2.txt
file: ./dir3/a file3.txt
file: ./dir1/a file1.txt
file: ./myscript
Ou quand j'utilise la 2e forme comme si:
$ find . -type f -exec ./myscript {} +
file: ./dir2/a file2.txt
file: ./dir3/a file3.txt
file: ./dir1/a file1.txt
file: ./myscript
Trouver + Xargs
Les 2 méthodes ci-dessus, bien que semblables différentes, sont essentiellement les mêmes. Le premier prend la sortie de la recherche, le divisant à l'aide de NULLS (\0
) via le -print0
Basculez pour trouver. Les xargs -0
est spécialement conçu pour prendre l'entrée qui est divisée à l'aide de NULLS. Cette syntaxe non standard a été introduite par GNU find
et xargs
mais se trouve également de nos jours dans quelques autres comme les plus récents BSD. Le -r
Option est nécessaire pour éviter d'appeler myscript
si find
ne trouve rien avec GNU find
mais pas avec BSDS.
NOTE : Cette approche complète se charge sur le fait que vous ne passerez jamais une chaîne extrêmement longue. Si c'est le cas, alors une 2ème invocation de ./myscript
sera lancé avec le reste des résultats ultérieurs de la recherche.
trouver avec +
C'est la manière standard (bien que ce n'ait été ajouté relativement récemment (2005) au GNU implémentation de find
). La capacité de faire ce que nous faisons avec xargs
est littéralement intégré à find
. SO find
_ trouvera une liste de fichiers, puis transmettez cette liste comme de nombreux arguments que possible à la commande spécifiée après -exec
(noter que {}
ne peut être que durer juste avant +
Dans ce cas), exécutez les commandes plusieurs fois si nécessaire.
Dans le premier exemple, nous prenons un raccourci en évitant complètement les problèmes avec la citation, en utilisant des nulls pour séparer les arguments. Lorsque xargs
reçoit cette liste, il est chargé de se faire scinder sur les NULLS protégeant efficacement nos atomes de commandement individuels.
Dans le deuxième exemple, nous gardons les résultats internes à find
et il sait donc ce que chaque fichier atom est-il et garantira de les gérer de manière appropriée, évitant ainsi l'entreprise Whoie de leur citer.
Cette question se présente de temps à autre pour un bonus que je l'ajoute à cette réponse, principalement pour que je puisse le trouver à l'avenir. Vous pouvez utiliser xargs
pour voir ce que la limite de l'environnement comme:
$ xargs --show-limits
Your environment variables take up 4791 bytes
POSIX upper limit on argument length (this system): 2090313
POSIX smallest allowable upper limit on argument length (all systems): 4096
Maximum length of command we could actually use: 2085522
Size of command buffer we are actually using: 131072
find . -name something.txt -exec myscript {} +
Dans ce qui précède, find
trouve tous les noms de fichiers correspondants et leur fournit comme des arguments à myscript
. Cela fonctionne avec des noms de fichier indépendamment des espaces ou de tout autre personnage impair.
Si tous les noms de fichier correspondent à une ligne, MyScript est exécuté une fois. Si la liste est trop longue pour que la coquille soit manipulée, trouvez-la à plusieurs reprises de MyScript au besoin.
Plus: Combien de fichiers correspondent à une ligne de commande? man find
dit que find
construit des lignes de commande informatique "de la même manière que Xargs construit sa". Et, man xargs
que les limites sont dépendantes du système et que vous pouvez les déterminer en fonctionnant xargs --show-limits
. (getconf ARG_MAX
est également une possibilité). Sur Linux, la limite est typiquement (mais pas toujours) environ 2 millions de caractères par ligne de commande.
Quelques ajout à la bonne réponse de @ slm.
La limitation de la taille des arguments est sur le execve(2)
appel système (en fait, il est de la taille cumulée des chaînes d'arguments et de l'environnement et des pointeurs). Si myscript
est écrit dans une langue que votre Shell peut interpréter, alors peut-être que vous n'avez pas besoin exécuter, vous pourriez avoir votre Shell interpréter simplement sans avoir à exécuter un autre interprète .
Si vous exécutez le script comme:
(. myscript x y)
C'est comme:
myscript x y
Sauf que cela est interprété par un enfant de la coquille actuelle, au lieu de exécution IT (qui finit par impliquer exécutantsh
(ou quelle que soit la ligne de SHA-BANG Spécifie le cas échéant) avec encore plus d'arguments).
Maintenant évidemment, vous ne pouvez pas utiliser find -exec {} +
avec le .
commande, comme .
être une commande intégrée de la coquille, il doit être exécuté par la coque, non par find
.
Avec zsh
, c'est facile:
IFS=$'\0'
(. myscript $(find ... -print0))
Ou:
(. myscript ${(ps:\0:)"$(find ... -print0)"}
Bien que avec zsh
, vous n'auriez pas besoin de find
en premier lieu car la plupart de ses fonctions sont intégrées à zsh
globbing.
bash
variables cependant ne peut pas contenir de caractères nul, vous devez donc trouver un autre moyen. Une façon pourrait être:
files=()
while IFS= read -rd '' -u3 file; do
files+=("$file")
done 3< <(find ... -print0)
(. myscript "${files[@]}")
Vous pouvez également utiliser une option globstar
option dans bash
4.0 et ultérieure:
shopt -s globstar failglob dotglob
(. myscript ./**/something.txt)
Noter que **
suivi des liens symboliques aux répertoires jusqu'à ce qu'il soit corrigé dans bash
4.3. Notez également que bash
_ ne met pas en œuvre zsh
qualificatifs de globe afin que vous n'obtiens pas toutes les fonctionnalités de find
là-bas.
Une autre alternative serait d'utiliser GNU ls
:
eval "files=(find ... -exec ls -d --quoting-style=Shell-always {} +)"
(. myscript "${files[@]}")
Les méthodes ci-dessus peuvent également être utilisées si vous souhaitez vous assurer que myscript
est exécuté une seule fois (échec si la liste des arguments est trop grande). Sur les versions récentes de Linux, vous pouvez augmenter et même soulever cette limitation de la liste des arguments avec:
ulimit -s 1048576
(Taille de pile de 1GIB, dont un quart peut être utilisé pour la liste Arg + env).
ulimit -s unlimited
(sans limites)
Dans la plupart des systèmes, il y a une limite sur la longueur d'une ligne de commande transmise à n'importe quel programme, en utilisant xargs
ou -exec command {} +
. De man find
:
-exec command {} +
This variant of the -exec action runs the specified command on
the selected files, but the command line is built by appending
each selected file name at the end; the total number of invoca‐
tions of the command will be much less than the number of
matched files. The command line is built in much the same way
that xargs builds its command lines. Only one instance of `{}'
is allowed within the command. The command is executed in the
starting directory.
Les invocations seront beaucoup moins nombreuses, mais ne sont pas garanties. Ce que vous devriez faire est de lire les noms de fichiers séparés nul séparés dans le script de STDIN, possibles sur la base d'un argument de commande de commande -o -
. Je ferais quelque chose comme:
$ find . -name something.txt -print0 | myscript -0 -o -
et mettre en œuvre les arguments d'option à myscript
en conséquence.