Hoe maak je bash glob een stringvariabele?

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

  • Enkele aanhalingstekens stoppen shell-interpolatie in Bash, dus je zou kunnen proberen om je variabele dubbel te citeren.
  • @ThomasN nee, dat werkt niet. k is een berekende string, en ik heb het nodig sta y op die manier tot de lus. Controleer mijn lange versie.
  • @ThomasN Ik heb de korte versie bijgewerkt om het duidelijker te maken.

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 gebruik eval 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 aanroepen eval daarop vraagt om problemen). U ‘ wilt globbing inschakelen voor for i in $dir, en for l in $k, maar niet voor for j in $exclude. U ‘ wilt een set -f voor de laatste en een set +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 voor echo $l, dus $l moet daar worden geciteerd.
  • @St é phaneChazelas verwijst u naar v1 of v2? Voor v2 staan zowel exclude en dirs 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=* en foo='*' is hetzelfde. Maar echo $foo en echo "$foo" zijn dat niet (in shells zoals bash, 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 in zsh, 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 

Geef een reactie

Het e-mailadres wordt niet gepubliceerd. Vereiste velden zijn gemarkeerd met *