Hvordan laver man bash glob til en strengvariabel?

Systeminfo

OS: OS X

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

Baggrund

Jeg ønsker, at tidsmaskinen skal udelukke et sæt mapper og filer fra alt mit git / nodejs-projekt. Mine projektmapper findes i ~/code/private/ og ~/code/public/ så jeg prøver at brug bash looping til at udføre tmutil.

Udgave

Kort version

Hvis jeg har en beregnet strengvariabel k, hvordan får jeg det til at kugle ind eller lige 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 version nedenfor vil du se k=$i/$j. Så jeg kan ikke kode strengen i for-sløjfen.

Lang 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 er ikke globbed. Ikke hvad 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 shell-interpolering i Bash, så du kan prøve at citere din variabel dobbelt.
  • @ThomasN nej, det virker ikke. k er en beregnet streng, og jeg har brug for den sta y den måde indtil løkken. Kontroller min lange version.
  • @ThomasN Jeg opdaterede den korte version for at gøre den klarere.

Svar

Du kan tvinge en ny evalueringsrunde med eval, men det er faktisk ikke nødvendigt. (Og eval starter har alvorlige problemer i det øjeblik, dine filnavne indeholder specialtegn som $.) Problemet er ikke med globbing, men med tilde-udvidelsen.

Globbing sker efter variabeludvidelse, hvis variablen ikke er citeret, som her (*) :

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

Så, på samme måde svarer det til 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 betyder det ikke:

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

Dette er tydeligt dokumenteret til Bash:

Rækkefølgen af udvidelser er: afstivningsudvidelse; tildeudvidelse, parameter og variabel udvidelse, …

Tilde-udvidelse sker før variabel udvidelse, så tildes inden i variabler ikke udvides. Den nemme løsning er at bruge $HOME eller den fulde sti i stedet.

(* at udvide globs fra variabler er normalt ikke det, du vil have)


En anden ting:

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

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

bemærk, at da $exclude ikke citeres, er den både delt og også globbed på dette tidspunkt. Så hvis den aktuelle mappe indeholder noget, der matcher mønsteret, udvides 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 at omgå dette skal du bruge 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 array-poster også indeholde mellemrum uden problemer med opdeling.


Noget lignende kan gøres med find -path, hvis du ikke har noget imod hvilket katalogniveau de målrettede filer skal være. F.eks. at finde en sti, der slutter 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 skal bruge $HOME i stedet for ~ af samme grund som før, og $dirs skal citeres på find kommandolinje, så den bliver delt, men $pattern bør citeres, så den ikke ved et uheld udvides af skallen.

(Jeg tror, du kunne lege med -maxdepth på GNU finde ud af at begrænse, hvor dybt søgningen går, hvis du er ligeglad, men det er lidt af et andet problem.)

Kommentarer

  • Er du det eneste svar med find? Jeg udforsker faktisk den rute også, da for-loop bliver kompliceret. Men jeg har problemer med ‘ -stien ‘.
  • Kredit til dig som din info om tilde ‘ ~ ‘ er mere direkte til hovedproblemet. Jeg vil sende det endelige script og forklaringen i et andet svar. Men fuld kredit til dig: D
  • @JohnSiu, ja, ved hjælp af find var det, der først kom op i tankerne. Det kan også være anvendeligt afhængigt af det nøjagtige behov. (eller bedre for nogle anvendelser.)
  • @kevinarpe, jeg tror, at arrays grundlæggende er beregnet til netop det, og ja, "${array[@]}" (med citaterne! ) er dokumenteret (se her og her ) for at udvide til elementerne som forskellige ord uden opdele dem yderligere.
  • @sixtyfive, ja, [abc] er en standard del af glob mønstre , som ?, jeg tror ikke ‘ Jeg tror ikke det ‘ er nødvendigt at dække dem alle her .

Svar

Du kan gemme det som en matrix i stedet for en streng til at bruge senere i mange tilfælde og lad kuglen ske, når du definerer den. I dit tilfælde, for eksempel:

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

eller i det senere eksempel, du “skal eval nogle af 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

  • Bemærk hvordan $exclude indeholder jokertegn, du ‘ d har brug for at deaktivere globbing, før du bruger split + glob operatøren på den og gendanner den til $i/$j og ikke brug eval men brug "$i"/$j
  • Både du og ilkkachu giver et godt svar. Men hans svar identificerede problemet. Så kredit til ham.

Svar

@ilkkachu svar løste det største globale problem. Fuld kredit til ham.

V1

På grund af exclude, der indeholder poster både med og uden jokertegn (*), og de findes muligvis ikke alt i alt er der behov for ekstra kontrol efter globbing af $i/$j. Jeg deler mine fund 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 

Output Explaination

Følgende er den delvise output for at forklare 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! ^^^ 

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 ekskluderingsposten ($j) har intet jokertegn, $i/$j bliver en sammenkædning af almindelig streng. Filen / dir eksisterer dog 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 post ($j) jokertegn, men har ingen fil- / katalogmatch, globbing af $i/$j returnerer bare den originale streng.

V2

V2 brug et enkelt citat, eval og shopt -s nullglob for at få et rent resultat. Ingen endelig kontrol af fil / dir kræves.

#!/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, globs i $exclude kunne blive udvidet på tidspunktet for den $exclude udvidelse (og opkald eval om det beder om problemer). Du ‘ vil have globbing aktiveret for for i in $dir og for l in $k, men ikke til for j in $exclude. Du ‘ vil have en set -f før sidstnævnte og en set +f til den anden. Mere generelt vil du ‘ gerne indstille din split + glob-operator, inden du bruger den. Under alle omstændigheder ønsker du ikke ‘ t split + glob til echo $l, så $l skal citeres der.
  • @St é phaneChazelas henviser du til v1 eller v2? For v2 er både exclude og dirs i et enkelt citat (), so no globbing till eval`.
  • Globbing finder sted ved ikke-citeret variabel udvidelse i listesammenhæng , at (at lade en variabel ikke citeres) er det vi kalder undertiden operatoren split + glob . Der er ‘ ingen globbing i tildelinger til skalarvariabler. foo=* og foo='*' er det samme. Men echo $foo og echo "$foo" er ikke (i skaller som bash, det ‘ er rettet i skaller som zsh, fisk eller rc, se også linket ovenfor). Her gør du ønsker at bruge denne operatør, men nogle steder kun den delte del og andre kun glob-delen.
  • @St é phaneChazelas Tak for info !!! Tog mig engang, men jeg forstår bekymringen nu. Dette meget værdifuldt !! Tak !!!

Svar

Med zsh:

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

${^array}string skal udvides som $array[1]string $array[2]string.... $=var er at udføre ordopdeling på variablen (noget andre skaller gør som standard!), $~var klodser på variablen (noget andet skaller også som standard (når du generelt ikke vil have dem til det, har du været nødt til at citere $f ovenfor i andre skaller)).

(N) er en glob-kvalifikator, der aktiverer nullglob for hver af disse globs, der er resultatet af den $^array1/$^array2 udvidelse. Det får globus til at udvide sig til ingenting, når de ikke matcher. Det sker også for at gøre en ikke-glob som ~/code/private/foo/Thumbs.db til en, hvilket betyder, at hvis den pågældende ikke eksisterer, det er ikke inkluderet.

Kommentarer

  • Dette er virkelig rart. Jeg testede og fungerer. Det ser dog ud til, at zsh er mere følsom over for newline, når ved hjælp af enkelt citat. Den måde, exclude er lukket på, påvirker output.
  • @JohnSiu, åh ja, du ‘ har ret. Det ser ud til, at split + glob og $^array skal udføres i to separate trin for at sikre, at de tomme elementer kasseres (se redigering). Det ligner lidt fejl i zsh, jeg ‘ Jeg rejser problemet på deres mailingliste.
  • Jeg kommer med en v2 til bash, som er renere, men stadig ikke så kompakt som dit zsh-script, lol

Svar

Gammel post, men jeg snublede ind her så t troede, at dette måske kan hjælpe andre 🙂

Der er en kinda-glob () -funktion i bash … den kaldes compgen -G Den udsender en værdi pr. linje så har brug for en readarray for at arbejde.

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 

Skriv et svar

Din e-mailadresse vil ikke blive publiceret. Krævede felter er markeret med *