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
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 paseval
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 appeleval
sur cela pose des problèmes). Vous ‘ voulez que le glissement soit activé pourfor i in $dir
etfor l in $k
, mais pas pourfor j in $exclude
. Vous ‘ voulez unset -f
avant ce dernier et unset +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 pourecho $l
, donc$l
doit être cité ici. - @St é phaneChazelas faites-vous référence à la v1 ou à la v2? Pour la version 2,
exclude
etdirs
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=*
etfoo='*'
est identique. Maisecho $foo
etecho "$foo"
ne le sont pas (dans des coques commebash
, ‘ 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 danszsh
, 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
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.