Come rendere bash glob una variabile stringa?


14

Informazioni di sistema

OS: OS X

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

sfondo

Voglio che la macchina del tempo escluda un set di directory e file da tutto il mio progetto git / nodejs. Le mie directory di progetto sono presenti ~/code/private/e ~/code/public/quindi sto cercando di usare il loop bash per fare il tmutil.

Problema

Versione breve

Se ho una variabile stringa calcolatak , come posso renderla glob o appena prima di un ciclo for:

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

Nella versione lunga di seguito, vedrai k=$i/$j. Quindi non posso hardcodificare la stringa nel ciclo for.

Versione lunga

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

Produzione

Non sono turbati. Non quello che voglio.

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

Le virgolette singole interrompono l'interpolazione della shell in Bash, quindi potresti provare a virgolette doppie per la tua variabile.
Thomas N,

@ThomasN no, non funziona. kè una stringa calcolata e ho bisogno che rimanga tale fino al ciclo. Si prega di controllare la mia versione lunga.
John Siu,

@ThomasN Ho aggiornato la versione breve per renderla più chiara.
John Siu,

Risposte:


18

Puoi forzare un altro giro di valutazione con eval, ma non è effettivamente necessario. (E evalinizia ad avere seri problemi nel momento in cui i nomi dei tuoi file contengono caratteri speciali come $.) Il problema non è con il globbing, ma con l'espansione della tilde.

Il globbing si verifica dopo l' espansione della variabile, se la variabile non è quotata, come qui (*) :

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

Allo stesso modo, questo è simile a quello che hai fatto e funziona:

$ 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

Ma con la tilde non lo fa:

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

Questo è chiaramente documentato per Bash:

L'ordine delle espansioni è: espansione delle parentesi graffe; espansione tilde, espansione parametri e variabili, ...

L'espansione della tilde avviene prima dell'espansione della variabile, quindi le tilde all'interno delle variabili non vengono espanse. La soluzione semplice è utilizzare $HOMEinvece il percorso completo.

(* Espandere globs dalle variabili di solito non è quello che vuoi)


Un'altra cosa:

Quando esegui il ciclo sugli schemi, come qui:

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

si noti che poiché $excludenon è quotato, è sia diviso, sia anche sconvolto a questo punto. Quindi, se la directory corrente contiene qualcosa che corrisponde al modello, viene espansa in questo modo:

$ 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

Per aggirare questo, utilizzare una variabile di matrice anziché una stringa divisa:

$ 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

Come bonus aggiuntivo, le voci dell'array possono contenere anche spazi bianchi senza problemi con la divisione.


Qualcosa di simile potrebbe essere fatto find -path, se non ti dispiace a quale livello di directory dovrebbero essere i file di destinazione. Ad esempio, per trovare qualsiasi percorso che termina in /e2e/*.js:

$ dirs="$HOME/public $HOME/private"
$ pattern="*/e2e/*.js"
$ find $dirs -path "$pattern"
/home/foo/public/one/two/three/e2e/asdf.js

Dobbiamo usare $HOMEinvece che ~per lo stesso motivo di prima, e deve essere citato $dirsdalla findriga di comando in modo che venga diviso, ma $patterndovrebbe essere citato in modo che non venga accidentalmente espanso dalla shell.

(Penso che potresti giocare con -maxdepthGNU find per limitare la profondità della ricerca, se ti interessa, ma questo è un po 'un problema diverso.)


Sei l'unica risposta con find? In realtà sto esplorando anche questa strada, poiché il for-loop sta diventando complicato. Ma ho difficoltà con il 'percorso'.
John Siu,

Ringraziandoti perché le tue informazioni su tilde '~' sono più dirette al problema principale. Pubblicherò la sceneggiatura finale e la spiegazione in un'altra risposta. Ma pieno merito a te: D
John Siu,

@JohnSiu, sì, usare find è stato ciò che mi è venuto in mente per la prima volta. Potrebbe anche essere utilizzabile, a seconda della necessità esatta. (o meglio, per alcuni usi.)
ilkkachu,

1
@kevinarpe, penso che le matrici siano fondamentalmente pensate proprio per questo, e sì, "${array[@]}"(con le virgolette!) è documentato (vedi qui e qui ) per espandersi agli elementi come parole distinte senza dividerle ulteriormente.
ilkkachu,

1
@sixtyfive, beh, [abc]è una parte standard dei modelli glob , tipo ?, non penso che sia necessario andare a coprirli tutti qui.
ilkkachu,

4

È possibile salvarlo come array anziché come stringa da utilizzare in seguito in molti casi e lasciare che il globbing si verifichi quando lo si definisce. Nel tuo caso, ad esempio:

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

o nell'esempio successivo, avrai bisogno di evalalcune stringhe

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

1
Nota come $excludecontiene caratteri jolly, dovresti disabilitare il globbing prima di utilizzare l' operatore split + glob su di esso e ripristinarlo per il $i/$je non usarlo evalma usare"$i"/$j
Stéphane Chazelas

Sia tu che ilkkachu date una buona risposta. Tuttavia, la sua risposta ha identificato il problema. Quindi merito a lui.
John Siu,

2

La risposta di @ilkkachu ha risolto il problema principale del trauma. Credito completo per lui.

V1

Tuttavia, a causa del excludecontenimento di voci con e senza carattere jolly (*) e inoltre potrebbero non esistere affatto, è necessario un controllo extra dopo il globbing di $i/$j. Sto condividendo i miei risultati qui.

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

Spiegazione dell'output

Di seguito è riportato l'output parziale per spiegare la situazione.

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

Quanto sopra si spiega da sé.

/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

Quanto sopra appare perché la voce exclude ( $j) non ha caratteri jolly, $i/$jdiventa una semplice concatenazione di stringhe. Tuttavia, il file / dir non esiste.

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

Quanto sopra viene mostrato come exclude entry ( $j) contiene caratteri jolly ma non ha corrispondenza file / directory, il globbing di $i/$jrestituire semplicemente la stringa originale.

V2

V2 usa la virgoletta singola evale shopt -s nullglobper ottenere risultati chiari. Nessun controllo finale di file / dir richiesto.

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

Un problema è che in for j in $exclude, i globs in $excludepotrebbero espandersi al momento $excludedell'espansione (e invocare evalciò richiede problemi). Vorresti abilitare il globbing per for i in $dire for l in $k, ma non per for j in $exclude. Avresti bisogno di un set -fprima di quest'ultimo e di un set +faltro. Più in generale, è necessario ottimizzare l'operatore split + glob prima di utilizzarlo. In ogni caso, non vuoi split + glob per echo $l, quindi $ldovrebbe essere citato lì.
Stéphane Chazelas,

@ StéphaneChazelas ti riferisci a v1 o v2? Per v2, entrambi excludee dirssono in virgoletta singola ( ), so no globbing till eval`.
John Siu,

Il globbing si verifica in seguito a un'espansione variabile non quotata in contesti di elenco , che (lasciando una variabile non quotata) è ciò che talvolta chiamiamo operatore split + glob . Non ci sono problemi nelle assegnazioni alle variabili scalari. foo=*ed foo='*'è lo stesso. Ma echo $fooe echo "$foo"non lo sono (in shell come bash, è stato risolto in shell come zsh, fish o rc, vedere anche il link sopra). Qui non si desidera utilizzare tale operatore, ma in alcuni luoghi solo la parte di divisione, e in altri solo la parte glob.
Stéphane Chazelas,

@ StéphaneChazelas Grazie per le informazioni !!! Mi ha preso qualche volta ma ora capisco la preoccupazione. Questo molto prezioso !! Grazie!!!
John Siu,

1

Con zsh:

exclude='
*.launch
.classpath
.sass-cache
Thumbs.db
...
'

dirs=(
~/code/private/*
~/code/public/*
)

for f ($^dirs/${^${=~exclude}}(N)) {
  echo $f
}

${^array}stringè quello di espandersi come $array[1]string $array[2]string.... $=varè eseguire la suddivisione delle parole sulla variabile (qualcosa che altre shell fanno per impostazione predefinita!), $~varfa globbing sulla variabile (qualcosa che altre shell anche per impostazione predefinita (quando in genere non li vuoi, avresti dovuto citare $fsopra in altre conchiglie)).

(N)è un qualificatore glob che attiva nullglob per ciascuno di quei globs risultanti da quell'espansione $^array1/$^array2. Ciò rende i globs che si espandono nel nulla quando non corrispondono. Ciò accade anche per trasformare un non-glob come ~/code/private/foo/Thumbs.dbin uno, il che significa che se quel particolare non esiste, non è incluso.


Questo è davvero carino. Ho provato e funziona. Tuttavia, sembra che zsh sia più sensibile a newline quando si usa la virgoletta singola. Il modo in cui excludeè racchiuso sta influenzando l'output.
John Siu,

@JohnSiu, oh sì, hai ragione. Sembra lo split + glob e $^arraydeve essere fatto in due passaggi separati per assicurarsi che gli elementi vuoti vengano scartati (vedi modifica). Assomiglia un po 'a un bug zsh, solleverò il problema sulla loro mailing list.
Stéphane Chazelas,

Mi viene in mente un v2 per bash, che è più pulito, ma ancora non compatto come il tuo script zsh, lol
John Siu,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.