Comment faire de bash glob une variable de chaîne?

Informations système

OS: OS X

bash: GNU bash, version 3.2.57 (1) -release (x86_64-apple-darwin16)

Contexte

Je veux que Time Machine exclue un ensemble de répertoires et de fichiers de tous mes projets git / nodejs. Les répertoires de mon projet se trouvent dans ~/code/private/ et ~/code/public/ donc jessaye de utiliser la boucle bash pour faire le tmutil.

Problème

Version courte

Si jai un calculé variable de chaîne k, comment le rendre global ou juste avant un for- boucle:

i="~/code/public/*" j="*.launch" k=$i/$j # $k="~/code/public/*/*.launch" for i in $k # I need $k to glob here do echo $i done 

Dans la version longue ci-dessous, vous verrez k=$i/$j. Je ne peux donc pas coder en dur la chaîne dans la boucle for.

Version longue

#!/bin/bash exclude=" *.launch .classpath .sass-cache Thumbs.db bower_components build connect.lock coverage dist e2e/*.js e2e/*.map libpeerconnection.log node_modules npm-debug.log testem.log tmp typings " dirs=" ~/code/private/* ~/code/public/* " for i in $dirs do for j in $exclude do k=$i/$j # It is correct up to this line for l in $k # I need it glob here do echo $l # Command I want to execute # tmutil addexclusion $l done done done 

Sortie

Ils ne sont pas globulés. Pas ce que je veux.

~/code/private/*/*.launch ~/code/private/*/.DS_Store ~/code/private/*/.classpath ~/code/private/*/.sass-cache ~/code/private/*/.settings ~/code/private/*/Thumbs.db ~/code/private/*/bower_components ~/code/private/*/build ~/code/private/*/connect.lock ~/code/private/*/coverage ~/code/private/*/dist ~/code/private/*/e2e/*.js ~/code/private/*/e2e/*.map ~/code/private/*/libpeerconnection.log ~/code/private/*/node_modules ~/code/private/*/npm-debug.log ~/code/private/*/testem.log ~/code/private/*/tmp ~/code/private/*/typings ~/code/public/*/*.launch ~/code/public/*/.DS_Store ~/code/public/*/.classpath ~/code/public/*/.sass-cache ~/code/public/*/.settings ~/code/public/*/Thumbs.db ~/code/public/*/bower_components ~/code/public/*/build ~/code/public/*/connect.lock ~/code/public/*/coverage ~/code/public/*/dist ~/code/public/*/e2e/*.js ~/code/public/*/e2e/*.map ~/code/public/*/libpeerconnection.log ~/code/public/*/node_modules ~/code/public/*/npm-debug.log ~/code/public/*/testem.log ~/code/public/*/tmp ~/code/public/*/typings 

Commentaires

  • Les guillemets simples arrêtent linterpolation du shell dans Bash, vous pouvez donc essayer de mettre entre guillemets votre variable.
  • @ThomasN non, cela ne fonctionne pas. k est une chaîne calculée, et jen ai besoin y de cette façon jusquà la boucle. Veuillez vérifier ma version longue.
  • @ThomasN Jai mis à jour la version courte pour la rendre plus claire.

Réponse

Vous pouvez forcer un autre cycle dévaluation avec eval, mais ce nest » pas vraiment nécessaire. (Et eval commence avoir de sérieux problèmes au moment où vos noms de fichiers contiennent des caractères spéciaux comme $.) Le problème nest pas avec le globbing, mais avec lexpansion du tilde.

Le globbing se produit après expansion de variable, si la variable nest pas entre guillemets, comme ici (*) :

$ x="/tm*" ; echo $x /tmp 

Donc, dans la même veine, ceci est similaire à ce que vous avez fait, et fonctionne:

$ mkdir -p ~/public/foo/ ; touch ~/public/foo/x.launch $ i="$HOME/public/*"; j="*.launch"; k="$i/$j" $ echo $k /home/foo/public/foo/x.launch 

Mais avec le tilde, il ne « t:

$ i="~/public/*"; j="*.launch"; k="$i/$j" $ echo $k ~/public/*/*.launch 

Ceci est clairement documenté pour Bash:

Lordre des expansions est: expansion daccolades; expansion du tilde, expansion des paramètres et variables, …

Lexpansion du tilde se produit avant lexpansion des variables, donc les tildes à lintérieur des variables ne sont pas développées. La solution de contournement simple consiste à utiliser $HOME ou le chemin complet à la place.

(* lexpansion des globes à partir des variables nest généralement pas ce que vous voulez)


Autre chose:

Lorsque vous bouclez sur les motifs, comme ici:

exclude="foo *bar" for j in $exclude ; do ... 

notez que comme $exclude nest pas entre guillemets, il est à la fois fractionné et également globalisé à ce stade. Donc, si le répertoire actuel contient quelque chose correspondant au modèle, il est développé comme suit:

$ i="$HOME/public/foo" $ exclude="*.launch" $ touch $i/real.launch $ for j in $exclude ; do # split and glob, no match echo "$i"/$j ; done /home/foo/public/foo/real.launch $ touch ./hello.launch $ for j in $exclude ; do # split and glob, matches in current dir! echo "$i"/$j ; done /home/foo/public/foo/hello.launch # not the expected result 

Pour contourner ce problème, utilisez une variable de tableau au lieu dune chaîne divisée:

$ exclude=("*.launch") $ exclude+=("something else") $ for j in "${exclude[@]}" ; do echo "$i"/$j ; done /home/foo/public/foo/real.launch /home/foo/public/foo/something else 

En prime, les entrées de tableau peuvent également contenir des espaces sans problème de division.


Quelque chose de similaire pourrait être fait avec find -path, si vous ne vous souciez pas du niveau de répertoire des fichiers ciblés. Par exemple, pour trouver un chemin se terminant par /e2e/*.js:

$ dirs="$HOME/public $HOME/private" $ pattern="*/e2e/*.js" $ find $dirs -path "$pattern" /home/foo/public/one/two/three/e2e/asdf.js 

Nous devons utiliser $HOME au lieu de ~ pour la même raison que précédemment, et $dirs doit être décompressé sur le find ligne de commande pour quil soit divisé, mais $pattern doit être entre guillemets afin quil ne soit pas accidentellement développé par le shell.

(Je pense que vous pouvez jouer avec -maxdepth sur GNU find pour limiter la profondeur de la recherche, si vous vous en souciez, mais cest « un peu un problème différent.)

Commentaires

  • Êtes-vous la seule réponse avec find? En fait, jexplore aussi cette voie, car la boucle for devient compliquée. Mais je rencontre des difficultés avec le ‘ -path ‘.
  • Vous créditez vos informations sur le tilde ‘ ~ ‘ est plus direct au problème principal. Je publierai le script final et lexplication dans une autre réponse. Mais tout le crédit à vous: D
  • @JohnSiu, oui, lutilisation de find est ce qui mest venu à lesprit. Il peut également être utilisable, en fonction du besoin exact. (ou mieux aussi, pour certaines utilisations.)
  • @kevinarpe, je pense que les tableaux sont essentiellement destinés à cela, et oui, "${array[@]}" (avec les guillemets! ) est documenté (voir ici et ici ) pour développer les éléments en tant que mots distincts sans les diviser davantage.
  • @sixtyfive, eh bien, [abc] est une partie standard des modèles globaux , comme ?, je ne ‘ je pense pas que ‘ est nécessaire daller les couvrir tous ici .

Réponse

Vous pouvez lenregistrer sous forme de tableau au lieu dune chaîne à utiliser plus tard dans de nombreux cas et laissez le globbing se produire lorsque vous le définissez. Dans votre cas, par exemple:

 k=(~/code/public/*/*.launch) for i in "${k[@]}"; do  

ou dans lexemple suivant, vous « ll faudra eval certaines des chaînes

 dirs=(~/code/private/* ~/code/public/*) for i in "${dirs[@]}"; do for j in $exclude; do eval "for k in $i/$j; do tmutil addexclusion \"\$k\"; done" done done  

Commentaires

  • Notez comment $exclude contient des caractères génériques, vous ‘ d doit désactiver le globbing avant dutiliser lopérateur split + glob dessus et le restaurer pour $i/$j et pas eval mais utilisez "$i"/$j
  • Vous et ilkkachu avez donné une bonne réponse. Cependant, sa réponse a identifié le problème. à lui.

Réponse

La réponse @ilkkachu a résolu le principal problème de globbing. Tout le mérite lui revient.

V1

Cependant, en raison de exclude contenant des entrées avec et sans caractère générique (*), elles peuvent également ne pas exister t en tout, une vérification supplémentaire est nécessaire après le globbing de $i/$j. Je partage mes résultats ici.

#!/bin/bash exclude=" *.launch .DS_Store .classpath .sass-cache .settings Thumbs.db bower_components build connect.lock coverage dist e2e/*.js e2e/*.map libpeerconnection.log node_modules npm-debug.log testem.log tmp typings " dirs=" $HOME/code/private/* $HOME/code/public/* " # loop $dirs for i in $dirs; do for j in $exclude ; do for k in $i/$j; do echo -e "$k" if [ -f $k ] || [ -d $k ] ; then # Only execute command if dir/file exist echo -e "\t^^^ Above file/dir exist! ^^^" fi done done done 

Explication de la sortie

Voici la sortie partielle pour expliquer la situation.

/Volumes/HD2/JS/code/public/simple-api-example-ng2-express/a.launch ^^^ Above file/dir exist! ^^^ /Volumes/HD2/JS/code/public/simple-api-example-ng2-express/b.launch ^^^ Above file/dir exist! ^^^ /Volumes/HD2/JS/code/public/simple-api-example-ng2-express/.DS_Store ^^^ Above file/dir exist! ^^^ 

Ce qui précède est explicite.

/Volumes/HD2/JS/code/public/simple-api-example-ng2-express/.classpath /Volumes/HD2/JS/code/public/simple-api-example-ng2-express/.sass-cache /Volumes/HD2/JS/code/public/simple-api-example-ng2-express/.settings /Volumes/HD2/JS/code/public/simple-api-example-ng2-express/Thumbs.db /Volumes/HD2/JS/code/public/simple-api-example-ng2-express/bower_components /Volumes/HD2/JS/code/public/simple-api-example-ng2-express/build /Volumes/HD2/JS/code/public/simple-api-example-ng2-express/connect.lock /Volumes/HD2/JS/code/public/simple-api-example-ng2-express/coverage /Volumes/HD2/JS/code/public/simple-api-example-ng2-express/dist 

Ce qui précède apparaît parce que lentrée dexclusion ($j) na pas de caractère générique, $i/$j devient une concaténation de chaîne simple. Cependant, le fichier / répertoire nexiste pas.

/Volumes/HD2/JS/code/public/simple-api-example-ng2-express/e2e/*.js /Volumes/HD2/JS/code/public/simple-api-example-ng2-express/e2e/*.map 

Ce qui précède apparaît comme une entrée dexclusion ($j) contient caractère générique mais na pas de correspondance de fichier / répertoire, le glissement de $i/$j renvoie simplement la chaîne dorigine.

V2

V2 utilise des guillemets simples, eval et shopt -s nullglob pour obtenir un résultat net. Aucune vérification finale de fichier / répertoire nest requise.

#!/bin/bash exclude=" *.launch .sass-cache Thumbs.db bower_components build connect.lock coverage dist e2e/*.js e2e/*.map libpeerconnection.log node_modules npm-debug.log testem.log tmp typings " dirs=" $HOME/code/private/* $HOME/code/public/* " for i in $dirs; do for j in $exclude ; do shopt -s nullglob eval "k=$i/$j" for l in $k; do echo $l done shopt -u nullglob done done 

Commentaires

  • Un problème est que dans for j in $exclude, les globes de $exclude pourraient être développés au moment de cette $exclude expansion (et appel eval sur cela pose des problèmes). Vous ‘ voulez que le glissement soit activé pour for i in $dir et for l in $k, mais pas pour for j in $exclude. Vous ‘ voulez un set -f avant ce dernier et un set +f pour lautre. Plus généralement, vous ‘ souhaitez régler votre opérateur split + glob avant de lutiliser. Dans tous les cas, vous ‘ ne voulez pas de split + glob pour echo $l, donc $l doit être cité ici.
  • @St é phaneChazelas faites-vous référence à la v1 ou à la v2? Pour la version 2, exclude et dirs sont entre guillemets simples (), so no globbing till eval`.
  • Le globbing a lieu lors de l expansion dune variable non entre guillemets dans des contextes de liste , cest ce que (laisser une variable sans guillemets) est ce que nous parfois appeler lopérateur split + glob . Il ny a ‘ aucun agrégat dans les affectations aux variables scalaires. foo=* et foo='*' est identique. Mais echo $foo et echo "$foo" ne le sont pas (dans des coques comme bash, ‘ a été corrigé dans des coquilles comme zsh, fish ou rc, voir aussi le lien ci-dessus). Ici, vous faites veulent utiliser cet opérateur, mais à certains endroits seulement la partie scindée, et à dautres seulement la partie glob.
  • @St é phaneChazelas Merci pour linfo !!! ma pris un peu de temps mais je comprends le souci maintenant. Cest très précieux !! Merci !!!

Réponse

Avec zsh:

 exclude=" *.launch .classpath .sass-cache Thumbs.db ... " dirs=( ~/code/private/* ~/code/public/* ) for f ($^dirs/${^${=~exclude}}(N)) { echo $f }  

${^array}string doit se développer comme $array[1]string $array[2]string.... $=var consiste à effectuer un fractionnement de mot sur la variable (ce que font les autres shells par défaut!), $~var fait un glissement sur la variable (autre chose les shells également par défaut (quand vous ne le voulez généralement pas, vous « auriez dû citer $f ci-dessus dans dautres shells)).

(N) est un qualificatif global qui active nullglob pour chacun de ces globs résultant de ce $^array1/$^array2 expansion. Cela fait que les globes se développent à rien quand ils ne correspondent pas. Cela se produit également pour transformer un non-glob comme ~/code/private/foo/Thumbs.db en un, ce qui signifie que si ce particulier nexiste pas, il « nest pas inclus.

Commentaires

  • Cest vraiment sympa. Jai testé et fonctionne. Cependant, il semble que zsh soit plus sensible aux sauts de ligne quand en utilisant des guillemets simples. La manière dont exclude est inclus affecte la sortie.
  • @JohnSiu, oh oui, vous ‘ vous avez raison. Il semble que le split + glob et le $^array doivent être effectués en deux étapes distinctes pour vous assurer que les éléments vides sont supprimés (voir modification). Cela ressemble un peu à un bug dans zsh, je ‘ vais soulever le problème sur leur liste de diffusion.
  • Je propose une version v2 pour bash, qui est plus propre, mais toujours pas aussi compact que votre script zsh, lol

Answer

Ancien post mais je suis tombé ici alors t hought cela peut aider les autres 🙂

Il y a une fonction kinda-glob () dans bash … elle sappelle compgen -G Elle affiche une valeur par ligne donc a besoin dun readarray pour fonctionner.

Essayez ceci:

i="~/code/public/*" j="*.launch" k=$i/$j # $k="~/code/public/*/*.launch" readarray -t files < <(compgen -G "$k") # $k is globbed here for i in "${files[@]}" do echo "Found -$i-" done 

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *