Informații despre sistem
OS: OS X
bash: GNU bash, versiunea 3.2.57 (1) -release (x86_64-apple-darwin16)
Fundal
 Vreau ca mașina timpului să excludă un set de directoare și fișiere din tot proiectul meu git / nodejs. Directoarele mele de proiect sunt în  ~/code/private/  și  ~/code/public/  așa că încerc să folosiți bash looping pentru a face tmutil. 
Problemă
Versiune scurtă
 Dacă am un  calculat  variabilă de șir k, cum o fac să fie glob sau chiar înainte de o for- buclă: 
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 
 În versiunea lungă de mai jos, veți vedea k=$i/$j. Deci, nu pot codifica șirul în bucla for. 
Versiune lungă
#!/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 
Ieșire
Acestea nu sunt globulate. Nu ceea ce vreau.
~/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 
Comentarii
Răspuns
 Puteți forța o altă rundă de evaluare cu eval, dar acest lucru nu este de fapt necesar. (Și eval începe având probleme grave în momentul în care numele fișierelor dvs. conțin caractere speciale, cum ar fi $.) Problema nu este cu glob, ci cu expansiunea tilde. 
Globbing se întâmplă după expansiunea variabilei, dacă variabila este necotată, ca aici (*) :
$ x="/tm*" ; echo $x /tmp 
Deci, în același sens, acest lucru este similar cu ceea ce ați făcut și funcționează:
$ 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 
Dar cu tilde nu „t:
$ i="~/public/*"; j="*.launch"; k="$i/$j" $ echo $k ~/public/*/*.launch 
Acesta este clar documentat pentru Bash:
Ordinea expansiunilor este: expansiunea bretelei; extinderea tildei, parametrul și expansiunea variabilă, …
 Extinderea tildei are loc înainte de expansiunea variabilelor, astfel încât tildele din variabile nu sunt extinse. Soluția simplă este să folosiți $HOME sau calea completă în schimb. 
(* extinderea globurilor din variabile nu este de obicei ceea ce doriți)
Un alt lucru:
Când faceți o buclă peste modele, ca aici:
exclude="foo *bar" for j in $exclude ; do ... 
 rețineți că, deoarece $exclude nu este citat, acesta este atât divizat, cât și globulat în acest moment. Deci, dacă directorul curent conține ceva care se potrivește cu modelul, acesta este extins la: 
$ 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 
Pentru a rezolva acest lucru, utilizați o variabilă în loc de un șir divizat:
$ 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 
Ca bonus adăugat, intrările matrice pot conține și spații albe fără probleme cu divizarea.
 Ceva similar s-ar putea face cu find -path, dacă nu vă deranjează ce nivel de director ar trebui să fie fișierele vizate. De exemplu, pentru a găsi orice cale care se termină cu /e2e/*.js: 
$ dirs="$HOME/public $HOME/private" $ pattern="*/e2e/*.js" $ find $dirs -path "$pattern" /home/foo/public/one/two/three/e2e/asdf.js 
 Trebuie să folosim $HOME în loc de ~ din același motiv ca înainte și $dirs trebuie să fie necotat pe find linia de comandă, astfel încât să se împartă, dar $pattern ar trebui să fie citat, astfel încât să nu fie extins accidental de către shell. 
 (cred că ai putea juca cu -maxdepth pe GNU find pentru a limita cât de profundă este căutarea, dacă ți-e interesat, dar asta este „o problemă diferită.) 
Comentarii
-  Sunteți singurul răspuns cu 
find? De fapt, explorez și traseul respectiv, deoarece bucla for se complică. Dar întâmpin dificultăți cu ‘ -path ‘. - Vă acordăm credit ca informație despre tilde ‘ ~ ‘ este mai direct la problema principală. Voi posta scenariul final și explicația într-un alt răspuns. Dar meritul complet pentru dvs.: D
 - @JohnSiu, da, folosirea găsirii a fost ceea ce mi-a venit în minte. Ar putea fi utilizabil și în funcție de necesitatea exactă. (sau mai bine, pentru unele utilizări.)
 -  @kevinarpe, cred că matricile sunt practic destinate doar acestui lucru și da, 
"${array[@]}"(cu ghilimele! ) este documentat (vezi aici și aici ) pentru a extinde elementele ca cuvinte distincte fără despărțindu-le mai departe. -  @sixtyfive, ei bine, 
[abc]este o parte standard a tipare glob , precum?, nu cred ‘ nu cred că este necesar ‘ să le acoperim pe toate aici . 
Răspuns
Puteți să-l salvați ca o matrice în loc de un șir de utilizat ulterior în multe cazuri și lăsați globul să se întâmple când îl definiți. În cazul dvs., de exemplu:
 k=(~/code/public/*/*.launch) for i in "${k[@]}"; do  
 sau în exemplul ulterior, „Va trebui să eval unele dintre șiruri 
 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  
Comentarii
-  Rețineți cum 
$excludeconține metacaractere, dvs. ‘ d trebuie să dezactivați globging-ul înainte de a utiliza operatorul split + glob pe acesta și să-l restaurați pentru$i/$jși nuevaldar utilizați"$i"/$j - Atât dvs., cât și ilkkachu dați un răspuns bun. Cu toate acestea, răspunsul său a identificat problema. către el.
 
Răspunsul
Răspunsul @ilkkachu a rezolvat problema principală. p>
V1
 Cu toate acestea, datorită exclude care conține intrări atât cu, cât și fără metacaracter (*), și, de asemenea, este posibil să nu existe În total, este necesară o verificare suplimentară după blocarea $i/$j. Îmi împărtășesc constatările aici. 
#!/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 
Explicația rezultatului
Urmează rezultatul parțial pentru a explica situația.
/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! ^^^ 
Cele de mai sus sunt explicative.
/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 
 Cele de mai sus se afișează deoarece intrarea de excludere ($j) nu are wildcard, $i/$j devine o concatenare de șir simplu. Cu toate acestea, fișierul / dir nu există. 
/Volumes/HD2/JS/code/public/simple-api-example-ng2-express/e2e/*.js /Volumes/HD2/JS/code/public/simple-api-example-ng2-express/e2e/*.map 
 Cele de mai sus apar ca intrare de excludere ($j) conțin wildcard, dar nu are nicio potrivire de fișier / director, blocarea $i/$j returnează doar șirul original. 
V2
 V2 folosește ghilimele unice, eval și shopt -s nullglob pentru a obține un rezultat curat. Nu necesită verificarea finală a fișierului / directorului. 
#!/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 
Comentarii
-  O problemă este că în 
for j in $exclude, globurile din$excludear putea fi extinse în momentul acelei$excludeevalpe care cere probleme). ‘ doriți ca globul să fie activat pentrufor i in $dirșifor l in $k, dar nu pentrufor j in $exclude. ‘ doriți unset -fînainte de acesta din urmă și unset +fpentru celălalt. Mai general, ‘ doriți să vă reglați operatorul split + glob înainte de al utiliza. În orice caz, nu ‘ nu doriți split + glob pentruecho $l, deci$lar trebui să fie citat acolo. -  @St é phaneChazelas vă referiți la v1 sau v2? Pentru v2, atât 
exclude, cât șidirssunt în ghilimel unic (), so no globbing tilleval`. -   Globarea are loc la variabila necotată  expansiune  în contexte de listă , ceea ce (lăsând o variabilă necotată) este ceea ce uneori apelați operatorul  split + glob . Nu există ‘ niciun glob în atribuirea variabilelor scalare. 
foo=*șifoo='*'este același. Darecho $fooșiecho "$foo"nu sunt (în cochilii precumbash, ‘ a fost fixat în cochilii precum zsh, pește sau rc, vezi și linkul de mai sus). Aici faci doresc să folosesc acel operator, dar în unele locuri doar partea divizată, iar în altele doar partea glob. - @St é phaneChazelas Vă mulțumim pentru informațiile !!! M-a luat cândva, dar înțeleg îngrijorarea acum. Acest lucru este foarte valoros !! Vă mulțumim !!!
 
Răspuns
 Cu zsh: 
 exclude=" *.launch .classpath .sass-cache Thumbs.db ... " dirs=( ~/code/private/* ~/code/public/* ) for f ($^dirs/${^${=~exclude}}(N)) { echo $f }  
 ${^array}string este să se extindă ca $array[1]string $array[2]string.... $=var este de a efectua împărțirea cuvintelor asupra variabilei (ceva ce alte shell-uri fac în mod implicit!), $~var face globul pe variabilă (altceva shell-uri, de asemenea, în mod implicit (atunci când în general nu doriți ca acestea, ar fi trebuit să citiți $f mai sus în alte shell-uri)).
 (N) este un calificativ glob care activează  nullglob  pentru fiecare dintre acele globuri rezultate din acel $^array1/$^array2 expansiune. Acest lucru face ca globii să se extindă la nimic atunci când nu se potrivesc. Acest lucru se întâmplă și să transforme un non-glob ca ~/code/private/foo/Thumbs.db într-unul, ceea ce înseamnă că, dacă acel anumit nu există, nu este inclus. 
Comentarii
-  Este foarte frumos. Am testat și funcționează. Cu toate acestea, se pare că zsh este mai sensibil la newline când folosind ghilimel unic. Modul în care este inclus 
excludeafectează ieșirea. -  @JohnSiu, oh da, tu ‘ are dreptate. Se pare că split + glob și 
$^arraytrebuie făcute în doi pași separați pentru a vă asigura că elementele goale sunt aruncate (vezi editarea). Asta arată un pic ca eroare înzsh, voi ‘ voi ridica problema pe lista lor de corespondență. - Am venit cu un v2 pentru bash, care este mai curat, dar totuși nu este la fel de compact ca scriptul dvs. zsh, lol
 
Răspuns
Vechi post, dar m-am împiedicat aici, așa că t cred că acest lucru îi poate ajuta pe ceilalți 🙂
Există o funcție kinda-glob () în bash … se numește compgen -G Se afișează o valoare pe linie, deci are nevoie de un readarray pentru a funcționa.
Încercați acest lucru:
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 
keste un șir calculat și am nevoie de el sta în acest fel până la buclă. Vă rugăm să verificați versiunea mea lungă.