Hur gör man bash glob till en strängvariabel?

Systeminformation

OS: OS X

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

Bakgrund

Jag vill att tidsmaskinen ska utesluta en uppsättning kataloger och filer från allt mitt git / nodejs-projekt. Mina projektkataloger finns i ~/code/private/ och ~/code/public/ så jag försöker använd bash looping för att göra tmutil.

Utgåva

Kortversion

Om jag har en beräknat strängvariabel k, hur gör jag det globalt i eller precis före en loop:

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 

I den långa versionen nedan ser du k=$i/$j. Så jag kan inte hårddiska strängen i för-slingan.

Lång version

#!/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 

Output

De är inte globbed. Inte vad jag vill.

~/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 

Kommentarer

  • Enstaka citat stoppar skalinterpolering i Bash, så du kan prova att dubbla citatet på din variabel.
  • @ThomasN nej, det fungerar inte. k är en beräknad sträng, och jag behöver den sta y det sättet till slingan. Kontrollera min långa version.
  • @ThomasN Jag uppdaterade den korta versionen för att göra den tydligare.

Svar

Du kan tvinga igen en utvärderingsomgång med eval, men det är egentligen inte nödvändigt. (Och eval startar har allvarliga problem i det ögonblick som dina filnamn innehåller specialtecken som $.) Problemet är inte med globbing men med tilde-expansionen.

Globbing händer efter variabelutvidgning, om variabeln inte citeras, som här (*) :

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

Så, på samma sätt liknar det som du gjorde och fungerar:

$ 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 

Men med tilde gör det inte:

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

Detta är tydligt dokumenterat för Bash:

Utökningsordningen är: hängsexpansion; tildexpansion, parameter och variabel expansion, …

Tilde-expansion sker före variabel expansion så att tildes i variabler inte expanderas. Den enkla lösningen är att använda $HOME eller hela sökvägen istället.

(* expandera globs från variabler är vanligtvis inte vad du vill)


En annan sak:

När du slingrar över mönstren, som här:

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

Observera att eftersom $exclude inte citeras, så är den både delad och också globerad vid denna tidpunkt. Så om den aktuella katalogen innehåller något som matchar mönstret utvidgas det till det:

$ 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 

För att kringgå detta, använd en arrayvariabel istället för en delad sträng:

$ 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 

Som en extra bonus kan matrisposter också innehålla tomt utrymme utan problem med delning.


Något liknande kan göras med find -path, om du inte bryr dig om vilken katalognivå de riktade filerna ska vara. T.ex. för att hitta en sökväg som slutar med /e2e/*.js:

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

Vi måste använda $HOME istället för ~ av samma anledning som tidigare, och $dirs måste citeras på find kommandorad så att den delas, men $pattern bör citeras så att den inte av misstag expanderas av skalet.

(Jag tror att du kan spela med -maxdepth på GNU och hitta för att begränsa hur djup sökningen går, om du bryr dig, men det är lite annorlunda.)

Kommentarer

  • Är du svaret med find? Jag utforskar faktiskt den vägen också, eftersom for-loop blir alltmer komplicerat. Men jag har svårt med ’ -stigen ’.
  • Tack till dig som din information om tilde ’ ~ ’ är mer direkt till huvudfrågan. Jag kommer att lägga upp det slutliga manuset och förklaringen i ett annat svar. Men full beröm till dig: D
  • @JohnSiu, ja, det var först att tänka på att använda Sök. Det kan också vara användbart, beroende på det exakta behovet. (eller bättre också, för vissa användningsområden.)
  • @kevinarpe, jag tror att matriser egentligen är avsedda för just det, och ja, "${array[@]}" (med citaten! ) är dokumenterad (se här och här ) för att expandera till elementen som distinkta ord utan dela dem ytterligare.
  • @sixtyfive, ja, [abc] är en standarddel av globmönster , som ?, jag tänker inte ’ att det ’ är nödvändigt för att täcka dem alla här .

Svar

Du kan spara det som en matris istället för en sträng för att använda senare i många fall och låt klotet hända när du definierar det. I ditt fall, till exempel:

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

eller i det senare exemplet, du ”Jag måste eval några av strängarna

 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  

Kommentarer

  • Observera hur $exclude innehåller jokertecken, du ’ d måste inaktivera globbing innan du använder split + glob operatören på den och återställa den för $i/$j och inte användning eval men använd "$i"/$j
  • Både du och ilkkachu ger bra svar. Men hans svar identifierade problemet. Så kredit till honom.

Svar

@ilkkachu svar löste huvudfrågan. Full kredit till honom.

V1

På grund av exclude som innehåller poster både med och utan jokertecken (*), och de kanske inte existerar Sammantaget krävs extra kontroll efter att globen $i/$j. Jag delar mina resultat här.

#!/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 

Förklaring av utdata

Nedan följer den partiella utdata för att förklara situationen.

/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! ^^^ 

Ovanstående är självförklarande.

/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 

Ovanstående visas eftersom exkluderingsposten ($j) har inget jokertecken, $i/$j blir en vanlig strängkoncentration. Filen / dir finns dock inte.

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

Ovanstående visas som exkludera posten ($j) jokertecken men har ingen fil / katalogmatchning, globbing av $i/$j returnerar bara originalsträngen.

V2

V2 använder enbart citat, eval och shopt -s nullglob för att få ett rent resultat. Ingen fil / dir slutkontroll krävs.

#!/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 

Kommentarer

  • Ett problem är att i for j in $exclude, globerna i $exclude kan utvidgas vid tiden för den $exclude -utvidgningen (och samtal eval om det ber om problem). Du ’ vill att globbing är aktiverad för for i in $dir och for l in $k, men inte för for j in $exclude. Du ’ vill ha en set -f före den senare och en set +f för den andra. Mer allmänt vill du ’ ställa in din split + glob-operatör innan du använder den. I alla fall vill du inte ’ t split + glob för echo $l, så $l ska citeras där.
  • @St é phaneChazelas hänvisar du till v1 eller v2? För v2 finns både exclude och dirs i enbart citat (), so no globbing till eval`.
  • Globbing äger rum vid citerad variabel expansion i listkontexter , att (lämnar en variabel unciterad) är vad vi kallar ibland operatorn split + glob . ’ är ingen globbing i tilldelningar till skalära variabler. foo=* och foo='*' är detsamma. Men echo $foo och echo "$foo" är inte (i skal som bash, det ’ har fixats i skal som zsh, fish eller rc, se även länken ovan). Här gör du vill använda den operatören, men på vissa ställen bara delad del, och på andra bara globdel.
  • @St é phaneChazelas Tack för info !!! Tog mig någon gång men jag förstår oro nu. Det här mycket värdefullt !! Tack !!!

Svar

Med zsh:

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

${^array}string är att expandera som $array[1]string $array[2]string.... $=var är att utföra orduppdelning på variabeln (något som andra skal gör som standard!), $~var gör globbing på variabeln (något annat skal också som standard (när du i allmänhet inte vill att de ska ha, måste du citera $f ovan i andra skal)).

(N) är en globkvalificering som aktiverar nullglob för var och en av dessa glober som härrör från den $^array1/$^array2 expansion. Det gör att globerna expanderar till ingenting när de inte matchar. Det händer också att en icke-glob som ~/code/private/foo/Thumbs.db blir en, vilket innebär att om det inte existerar, det ingår inte.

Kommentarer

  • Det här är riktigt trevligt. Jag testade och fungerar. Det verkar dock att zsh är mer känsligt för newline när med enstaka citat. Det sätt som exclude bifogas påverkar produktionen.
  • @JohnSiu, åh ja, du ’ har rätt. Det verkar som att split + glob och $^array måste göras i två separata steg för att se till att de tomma elementen kasseras (se redigera). Det ser lite ut som en fel i zsh, jag ’ kommer att ta upp frågan på deras e-postlista.
  • Jag kommer med en v2 för bash, vilket är renare men ändå inte så kompakt som ditt zsh-skript, lol

Svar

Gammalt inlägg men jag snubblade in här så t trodde att detta kan hjälpa andra 🙂

Det finns en kinda-glob () -funktion i bash … den heter compgen -G Den matar ut ett värde per rad så behöver en läsuppsättning för att fungera.

Prova detta:

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 

Lämna ett svar

Din e-postadress kommer inte publiceras. Obligatoriska fält är märkta *