web-dev-qa-db-fra.com

Utilisation d'une liste générée des noms de fichiers comme liste d'arguments - avec des espaces

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).

16
alexis

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 {} +

Exemple

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

Des détails

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.

Pourquoi pas citant?

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.

Taille maximale de la ligne de commande?

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
12
slm
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.

3
John1024

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)

2
Stéphane Chazelas

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.

1
Timo