Hvordan lage bash glob til en strengvariabel?

Systeminfo

OS: OS X

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

Bakgrunn

Jeg vil at tidsmaskinen skal ekskludere et sett med kataloger og filer fra alt mitt git / nodejs-prosjekt. Prosjektkatalogene mine er i ~/code/private/ og ~/code/public/ så jeg prøver å bruk bash looping for å gjøre tmutil.

Utgave

Kortversjon

Hvis jeg har en kalkulert strengvariabel k, hvordan får jeg den til å klippe inn eller rett før en 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 

I den lange versjonen nedenfor ser du k=$i/$j. Så jeg kan ikke hardkode strengen i for loop.

Lang versjon

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

Utgang

De er ikke globbed. Ikke hva jeg vil.

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

  • Enkelt anførselstegn stopper skallinterpolering i Bash, så du kan prøve å sitere variabelen din dobbelt.
  • @ThomasN nei, det fungerer ikke. k er en beregnet streng, og jeg trenger den sta y på den måten til løkken. Vennligst sjekk den lange versjonen min.
  • @ThomasN Jeg oppdaterte kortversjonen for å gjøre den tydeligere.

Svar

Du kan tvinge en ny evalueringsrunde med eval, men det er faktisk ikke nødvendig. (Og eval starter har alvorlige problemer i det øyeblikket filnavnene inneholder spesialtegn som $.) Problemet er ikke med globbing, men med tilde-utvidelsen.

Globbing skjer etter variabel utvidelse, hvis variabelen ikke er sitert, som her (*) :

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

Så, i samme retning, dette ligner på det du gjorde, og fungerer:

$ 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 gjør det ikke t:

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

Dette er tydelig dokumentert for Bash:

Rekkefølgen av utvidelser er: spenneutvidelse; tildeutvidelse, parameter og variabel utvidelse, …

Tilde-utvidelse skjer før variabel utvidelse, slik at tildes i variabler ikke utvides. Den enkle løsningen er å bruke $HOME eller hele banen i stedet.

(* å utvide globs fra variabler er vanligvis ikke det du vil ha)


En annen ting:

Når du løper over mønstrene, som her:

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

Vær oppmerksom på at da $exclude ikke er sitert, er den både delt, og også globbed på dette punktet. Så hvis den nåværende katalogen inneholder noe som samsvarer med mønsteret, utvides det til 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 

For å omgå dette, bruk en arrayvariabel i stedet for en splittet streng:

$ 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 ekstra bonus kan matriseoppføringer også inneholde mellomrom uten problemer med splitting.


Noe lignende kan gjøres med find -path, hvis du ikke har noe imot hvilket katalognivå de målrettede filene skal være. F.eks. for å finne en sti som slutter på /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å bruke $HOME i stedet for ~ av samme grunn som før, og $dirs må ikke siteres på find kommandolinje slik at den blir delt, men $pattern bør siteres slik at den ikke ved et uhell utvides av skallet.

(Jeg tror du kan leke med -maxdepth på GNU og finne for å begrense hvor dypt søket går, hvis du bryr deg, men det er litt av et annet problem.)

Kommentarer

  • Er du svaret med find? Jeg utforsker faktisk den ruten også, ettersom for-loop blir komplisert. Men jeg har problemer med ‘ -stien ‘.
  • Takk til deg som din informasjon om tilde ‘ ~ ‘ er mer direkte til hovedspørsmålet. Jeg vil legge ut det endelige manuset og forklaringen i et annet svar. Men full ære for deg: D
  • @JohnSiu, ja, bruk av finne var det som først kom opp i tankene dine. Det kan også være brukbart, avhengig av nøyaktig behov. (eller bedre også, for noen bruksområder.)
  • @kevinarpe, jeg tror matriser i utgangspunktet er ment for nettopp det, og ja, "${array[@]}" (med sitatene! ) er dokumentert (se her og her ) for å utvide til elementene som forskjellige ord uten splitte dem videre.
  • @sixtyfive, vel, [abc] er en standard del av globmønstre , som ?, jeg tror ikke ‘ Jeg tror ikke det ‘ er nødvendig å dekke dem alle her .

Svar

Du kan lagre det som en matrise i stedet for en streng som du kan bruke senere i mange tilfeller og la klatringen skje når du definerer det. I ditt tilfelle, for eksempel:

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

eller i det senere eksemplet, du «må eval noen av strengene

 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

  • Legg merke til hvordan $exclude inneholder jokertegn, du ‘ d trenger å deaktivere globbing før du bruker split + glob operatøren på den og gjenoppretter den for $i/$j og ikke bruk eval men bruk "$i"/$j
  • Både du og ilkkachu gir godt svar. Men svaret hans identifiserte problemet. Så æren til ham.

Svar

@ilkkachu svar løste hovedspørsmålet. Full ære til ham.

V1

På grunn av exclude som inneholder oppføringer både med og uten jokertegn (*), og de eksisterer kanskje ikke I det hele tatt er det behov for ekstra kontroll etter globbing av $i/$j. Jeg deler funnene mine her.

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

Forklaringer om utdata

Følgende er den delvise utdata for å forklare situasjonen.

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

Ovenstående er selvforklarende.

/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 

Ovenstående vises fordi ekskluderingsoppføringen ($j) har ikke et jokertegn, $i/$j blir en ren streng sammenkobling. Filen / dir eksisterer imidlertid ikke.

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

Ovenstående vises som ekskluder oppføring ($j) inneholder jokertegn, men har ingen samsvar med fil / katalog, globbing av $i/$j bare returner den opprinnelige strengen.

V2

V2 bruk enkelt sitat, eval og shopt -s nullglob for å få et rent resultat. Ingen endelig kontroll av fil / dir krever.

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

  • Et problem er at i for j in $exclude, globene i $exclude kunne utvides på tidspunktet for den $exclude utvidelsen (og ringer eval på det som ber om problemer). Du ‘ vil at globbing skal være aktivert for for i in $dir, og for l in $k, men ikke for for j in $exclude. Du ‘ vil ha en set -f før sistnevnte og en set +f for den andre. Mer generelt vil du ‘ innstille split + glob-operatøren før du bruker den. I alle fall vil du ikke ‘ ikke vil dele + glob for echo $l, så $l skal siteres der.
  • @St é phaneChazelas refererer du til v1 eller v2? For v2 er både exclude og dirs i ett sitat (), so no globbing till eval`.
  • Globbing foregår ved ikke sitert variabel utvidelse i listesammenhenger , at (å la en variabel ikke siteres) er det vi noen ganger kaller operatøren split + glob . ‘ er ingen globbing i tilordninger til skalarvariabler. foo=* og foo='*' er det samme. Men echo $foo og echo "$foo" er ikke (i skjell som bash, det ‘ er fikset i skjell som zsh, fisk eller rc, se også lenken over). Her gjør du vil bruke den operatøren, men noen steder bare den delte delen, og andre bare glob-delen.
  • @St é phaneChazelas Takk for info !!! Tok meg en gang, men jeg forstår bekymringen nå. Dette veldig verdifullt !! Takk skal du ha !!!

Svar

Med zsh:

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

${^array}string er å utvide som $array[1]string $array[2]string.... $=var er å utføre orddeling på variabelen (noe andre skjell gjør som standard!), $~var gjør globbing på variabelen (noe annet skjell også som standard (når du vanligvis ikke vil ha dem til, måtte du sitere $f ovenfor i andre skjell)).

(N) er en glob-kvalifisering som slår på nullglob for hver av disse globene som følge av at $^array1/$^array2 utvidelse. Det gjør at globene utvides til ingenting når de ikke samsvarer. Det skjer også for å gjøre en ikke-glob som ~/code/private/foo/Thumbs.db til en, noe som betyr at hvis det ikke eksisterer, det er ikke inkludert.

Kommentarer

  • Dette er veldig hyggelig. Jeg testet og fungerer. Det virker imidlertid som om zsh er mer følsomt for newline når bruker enkelt sitat. Måten exclude er vedlagt påvirker utgangen.
  • @JohnSiu, å ja, du ‘ har rett. Det ser ut til at split + glob og $^array må gjøres i to separate trinn for å sikre at de tomme elementene blir kastet (se redigering). Det ser litt ut som en feil i zsh, jeg ‘ Jeg tar opp problemet på adresselisten deres.
  • Jeg kommer med en v2 for bash, som er renere, men fortsatt ikke så kompakt som zsh-skriptet ditt, lol

Svar

Gammel post men jeg snublet inn her så t trodde dette kan hjelpe andre 🙂

Det er en kinda-glob () -funksjon i bash … den heter compgen -G Den gir en verdi per linje så trenger en readarray for å fungere. p>

Prøv dette:

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 

Legg igjen en kommentar

Din e-postadresse vil ikke bli publisert. Obligatoriske felt er merket med *