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
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ändningeval
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 samtaleval
om det ber om problem). Du ’ vill att globbing är aktiverad förfor i in $dir
ochfor l in $k
, men inte förfor j in $exclude
. Du ’ vill ha enset -f
före den senare och enset +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örecho $l
, så$l
ska citeras där. - @St é phaneChazelas hänvisar du till v1 eller v2? För v2 finns både
exclude
ochdirs
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=*
ochfoo='*'
är detsamma. Menecho $foo
ochecho "$foo"
är inte (i skal sombash
, 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 izsh
, 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
k
är en beräknad sträng, och jag behöver den sta y det sättet till slingan. Kontrollera min långa version.