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
$exclude
conț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 nueval
dar 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$exclude
ar putea fi extinse în momentul acelei$exclude
eval
pe 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 +f
pentru 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$l
ar trebui să fie citat acolo. - @St é phaneChazelas vă referiți la v1 sau v2? Pentru v2, atât
exclude
, cât șidirs
sunt în ghilimel unic (), so no globbing till
eval`. - 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
exclude
afectează ieșirea. - @JohnSiu, oh da, tu ‘ are dreptate. Se pare că split + glob și
$^array
trebuie 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
k
este 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ă.