Cum să faci din bash glob o variabilă de șir?

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

  • Ghilimelele simple opresc interpolarea shell-ului în Bash, deci ați putea încerca să citiți dublu variabila dvs.
  • @ThomasN nu, asta nu funcționează. 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ă.
  • @ThomasN Am actualizat versiunea scurtă pentru a o face mai clară.

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 nu eval 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 pentru for i in $dir și for l in $k, dar nu pentru for j in $exclude. ‘ doriți un set -f înainte de acesta din urmă și un set +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 pentru echo $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 și dirs 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=* și foo='*' este același. Dar echo $foo și echo "$foo" nu sunt (în cochilii precum bash, ‘ 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 în zsh, 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 

Lasă un răspuns

Adresa ta de email nu va fi publicată. Câmpurile obligatorii sunt marcate cu *