Systeeminfo
OS: OS X
bash: GNU bash, versie 3.2.57 (1) -release (x86_64-apple-darwin16)
Achtergrond
Ik wil dat de tijdmachine een set mappen en bestanden uitsluit van al mijn git / nodejs-projecten. Mijn projectmappen bevinden zich in ~/code/private/
en ~/code/public/
dus ik probeer gebruik bash looping om de tmutil
te doen.
Probleem
Korte versie
Als ik een berekend stringvariabele k
, hoe zorg ik ervoor dat deze glob in of vlak voor een for- 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
In de lange versie hieronder zie je k=$i/$j
. Dus ik kan de string niet hardcoderen in de for-lus.
Lange versie
#!/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
Uitvoer
Ze zijn niet globbed. Niet wat ik wil.
~/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
Reacties
Antwoord
Je kunt nog een evaluatieronde forceren met eval
, maar dat” is eigenlijk niet nodig. (En eval
begint ernstige problemen hebben op het moment dat uw bestandsnamen speciale tekens bevatten zoals $
.) Het probleem is niet met globbing, maar met de tilde-uitbreiding.
Globbing gebeurt na variabele expansie, als de variabele niet geciteerd is, zoals hier (*) :
$ x="/tm*" ; echo $x /tmp
Dus, in dezelfde geest is dit vergelijkbaar met wat je deed, en werkt:
$ 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
Maar met de tilde doet het niet “t:
$ i="~/public/*"; j="*.launch"; k="$i/$j" $ echo $k ~/public/*/*.launch
Dit is duidelijk gedocumenteerd voor Bash:
De volgorde van uitbreidingen is: uitbreiding met accolades; uitbreiding van tilde, uitbreiding van parameters en variabelen, …
Tilde-uitbreiding vindt plaats vóór variabele-uitbreiding, zodat tildes binnen variabelen niet worden uitgebreid. De gemakkelijke oplossing is om $HOME
of het volledige pad te gebruiken.
(* Globs uitbreiden van variabelen is meestal niet wat je wilt)
Nog iets:
Wanneer je over de patronen heen loopt, zoals hier:
exclude="foo *bar" for j in $exclude ; do ...
merk op dat, aangezien $exclude
niet geciteerd is, het op dit punt zowel gesplitst als globbed is. Dus als de huidige directory iets bevat dat overeenkomt met het patroon, wordt het uitgebreid tot dat:
$ 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
Om dit te omzeilen, gebruikt u een array-variabele in plaats van een gesplitste tekenreeks:
$ 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
Als een toegevoegde bonus kunnen array-items ook witruimte bevatten zonder problemen met splitsen.
Iets soortgelijks kan worden gedaan met find -path
, als u het niet erg vindt op welk mapniveau de beoogde bestanden zouden moeten zijn. Bijvoorbeeld om een pad te vinden dat eindigt op /e2e/*.js
:
$ dirs="$HOME/public $HOME/private" $ pattern="*/e2e/*.js" $ find $dirs -path "$pattern" /home/foo/public/one/two/three/e2e/asdf.js
We moeten $HOME
in plaats van ~
om dezelfde reden als hiervoor, en $dirs
moet niet worden geciteerd op de find
opdrachtregel zodat deze wordt gesplitst, maar $pattern
moet worden aangehaald zodat deze niet per ongeluk door de shell wordt uitgebreid.
(Ik denk dat je zou kunnen spelen met -maxdepth
op GNU find om te beperken hoe diep de zoekopdracht gaat, als het je kan schelen, maar dat “is een beetje een ander probleem.)
Reacties
- Ben jij het enige antwoord met
find
? Ik ben die route eigenlijk ook aan het verkennen, omdat de for-loop ingewikkeld wordt. Maar ik heb problemen met het ‘ -pad ‘. - Met dank aan u als uw informatie over tilde ‘ ~ ‘ is directer op het hoofdprobleem. Ik zal het laatste script en de uitleg in een ander antwoord plaatsen. Maar alle eer aan jou: D
- @JohnSiu, ja, het gebruik van find was het eerste dat in me opkwam. Het kan ook bruikbaar zijn, afhankelijk van de exacte behoefte. (of beter ook, voor sommige toepassingen.)
- @kevinarpe, ik denk dat arrays eigenlijk precies daarvoor bedoeld zijn, en ja,
"${array[@]}"
(met de aanhalingstekens! ) is gedocumenteerd (zie hier en hier ) om uit te breiden naar de elementen als afzonderlijke woorden zonder ze verder splitsen. - @sixtyfive, nou ja,
[abc]
is een standaardonderdeel van glob-patronen , zoals?
, ‘ denk ik dat het ‘ s nodig is om ze hier allemaal te bespreken .
Answer
Je kunt het opslaan als een array in plaats van een string om later in veel gevallen te gebruiken en laat de globbing gebeuren wanneer u het definieert. In uw geval bijvoorbeeld:
k=(~/code/public/*/*.launch) for i in "${k[@]}"; do
of in het latere voorbeeld “ll moet eval
enkele strings
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
Reacties
- Merk op hoe
$exclude
jokertekens bevat, jij ‘ d moet globbing uitschakelen voordat u de split + glob operator erop gebruikt en het herstellen voor de$i/$j
en niet gebruikeval
maar gebruik"$i"/$j
- Zowel jij als ilkkachu geven een goed antwoord. Zijn antwoord identificeerde het probleem echter. Dus aan hem.
Antwoord
@ilkkachu antwoord loste het belangrijkste globbing-probleem op. Alle eer aan hem.
V1
Echter, omdat exclude
vermeldingen zowel met als zonder jokerteken (*) bevat, en ze kunnen ook niet bestaan Al met al is extra controle nodig na het globben van $i/$j
. Ik deel mijn bevindingen hier.
#!/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
Uitleg over uitvoer
Hieronder volgt de gedeeltelijke uitvoer om de situatie uit te leggen.
/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! ^^^
Het bovenstaande spreekt voor zich.
/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
Het bovenstaande wordt weergegeven omdat het uitsluitingsitem ($j
) heeft geen jokerteken, $i/$j
wordt een gewone aaneenschakeling van tekenreeksen. Het bestand / dir bestaat echter niet.
/Volumes/HD2/JS/code/public/simple-api-example-ng2-express/e2e/*.js /Volumes/HD2/JS/code/public/simple-api-example-ng2-express/e2e/*.map
Het bovenstaande wordt weergegeven als uitsluitingsvermelding ($j
) bevat jokerteken maar heeft geen overeenkomst tussen bestand / map, de globbing van $i/$j
retourneert gewoon de originele string.
V2
V2 gebruik enkele aanhalingstekens, eval
en shopt -s nullglob
om een duidelijk resultaat te krijgen. Geen laatste controle van bestand / map vereist.
#!/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
Opmerkingen
- Een probleem is dat in
for j in $exclude
, de globs in$exclude
kunnen worden uitgebreid op het moment van die$exclude
uitbreiding (en aanroepeneval
daarop vraagt om problemen). U ‘ wilt globbing inschakelen voorfor i in $dir
, enfor l in $k
, maar niet voorfor j in $exclude
. U ‘ wilt eenset -f
voor de laatste en eenset +f
voor de andere. Meer in het algemeen zou u ‘ uw split + glob-operator willen afstemmen voordat u deze gebruikt. In elk geval wil je ‘ geen split + glob voorecho $l
, dus$l
moet daar worden geciteerd. - @St é phaneChazelas verwijst u naar v1 of v2? Voor v2 staan zowel
exclude
endirs
in enkele aanhalingstekens (), so no globbing till
eval`. - Globbing vindt plaats op niet-geciteerde variabele expansie in lijstcontexten , die (een variabele niet-geciteerd laten) is wat we roep soms de split + glob -operator aan. Er ‘ s geen globbing in toewijzingen aan scalaire variabelen.
foo=*
enfoo='*'
is hetzelfde. Maarecho $foo
enecho "$foo"
zijn dat niet (in shells zoalsbash
, het ‘ is gerepareerd in schelpen zoals zsh, fish of rc, zie ook de link hierboven). Hier doe je wil die operator gebruiken, maar op sommige plaatsen alleen het gesplitste gedeelte en op andere alleen het glob-gedeelte. - @St é phaneChazelas Bedankt voor de info !!! Heeft me een tijdje gekost, maar ik begrijp de bezorgdheid nu. Dit is zeer waardevol !! Dank je !!!
Antwoord
Met zsh
:
exclude=" *.launch .classpath .sass-cache Thumbs.db ... " dirs=( ~/code/private/* ~/code/public/* ) for f ($^dirs/${^${=~exclude}}(N)) { echo $f }
${^array}string
moet worden uitgebreid als $array[1]string $array[2]string...
. $=var
is om woordsplitsing uit te voeren op de variabele (iets wat andere shells standaard doen!), $~var
doet globbing op de variabele (iets anders shells ook standaard (als je dat over het algemeen niet wilt, “had je $f
hierboven in andere shells moeten citeren)).
(N)
is een glob-kwalificatie die nullglob inschakelt voor elk van die globs die het resultaat zijn van $^array1/$^array2
uitbreiding. Dat zorgt ervoor dat de globs zich uitbreiden naar niets als ze niet overeenkomen. Dat verandert ook een niet-glob-achtige ~/code/private/foo/Thumbs.db
in één, wat betekent dat als die specifieke niet bestaat, het is niet inbegrepen.
Opmerkingen
- Dit is echt leuk. Ik heb het getest en werkt. Het lijkt er echter op dat zsh gevoeliger is voor nieuwe regels wanneer met een enkel aanhalingsteken. De manier waarop
exclude
is ingesloten, heeft invloed op de uitvoer. - @JohnSiu, oh ja, jij ‘ kloppen. Het lijkt erop dat de split + glob en de
$^array
in twee afzonderlijke stappen moeten worden uitgevoerd om ervoor te zorgen dat de lege elementen worden weggegooid (zie bewerken). Dat lijkt een beetje op een bug inzsh
, ik ‘ zal het probleem op hun mailinglijst vermelden. - Ik bedenk een v2 voor bash, wat schoner is, maar nog steeds niet zo compact als je zsh-script, lol
Answer
Oud post maar ik strompelde hier zo t dacht dat dit anderen kan helpen 🙂
Er is een kinda-glob () functie in bash … het heet compgen -G Het geeft een waarde per regel af, dus heeft een readarray nodig om te werken.
Probeer dit:
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
is een berekende string, en ik heb het nodig sta y op die manier tot de lus. Controleer mijn lange versie.