Informazioni di sistema
OS: OS X
bash: GNU bash, versione 3.2.57 (1) -release (x86_64-apple-darwin16)
Background
Voglio che time machine escluda un insieme di directory e file da tutto il mio progetto git / nodejs. Le directory dei miei progetti si trovano in ~/code/private/
e ~/code/public/
quindi sto cercando di usa il ciclo bash per eseguire tmutil
.
Problema
Versione breve
Se ho un calcolata stringa variabile k
, come faccio a farla glob dentro o subito prima di un 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
Nella versione lunga sotto, vedrai k=$i/$j
. Quindi non posso codificare la stringa in il 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
Output
Non sono globbed. 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
Commenti
Risposta
Puoi forzare un altro ciclo di valutazione con eval
, ma” non è effettivamente necessario. E eval
inizia avere seri problemi nel momento in cui i nomi dei file contengono caratteri speciali come $
.) Il problema non è con il globb, ma con lespansione della tilde.
Il globbing si verifica dopo lespansione della variabile, se la variabile non è quotata, come qui (*) :
$ x="/tm*" ; echo $x /tmp
Quindi, allo stesso modo, è simile a quello che hai fatto tu 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 “t:
$ i="~/public/*"; j="*.launch"; k="$i/$j" $ echo $k ~/public/*/*.launch
Questo è chiaramente documentato per Bash:
Lordine delle espansioni è: espansione di parentesi graffe; espansione di tilde, espansione di parametri e variabili, …
Lespansione della tilde avviene prima dellespansione delle variabili, quindi le tilde allinterno delle variabili non vengono espanse. La soluzione più semplice è utilizzare $HOME
o il percorso completo.
(* lespansione di glob dalle variabili di solito non è ciò che desideri)
Unaltra cosa:
Quando si ripetono i modelli, come qui:
exclude="foo *bar" for j in $exclude ; do ...
nota che, poiché $exclude
non è quotato, a questo punto è sia diviso che globbato. Quindi, se la directory corrente contiene qualcosa che corrisponde al modello, viene espansa a questo:
$ 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 problema, utilizza una variabile di matrice invece di 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 dellarray possono anche contenere spazi bianchi senza problemi di suddivisione.
Qualcosa di simile potrebbe essere fatto con find -path
, se non ti dispiace quale livello di directory dovrebbero essere i file di destinazione. Ad esempio, per trovare un percorso che termina con /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 $HOME
invece di ~
per lo stesso motivo di prima e $dirs
deve essere deselezionato dal find
riga di comando in modo che venga suddivisa, ma $pattern
dovrebbe essere quotata in modo che non venga “t espansa accidentalmente dalla shell.
(Penso che potresti giocare con -maxdepth
su GNU find per limitare la profondità della ricerca, se ti interessa, ma questo “è un po un problema diverso.)
Commenti
- Sei lunica risposta con
find
? In realtà sto esplorando anche quel percorso, poiché il ciclo for si sta complicando. Ma ho difficoltà con il ‘ -path ‘. - Ti ringrazio per le tue informazioni su tilde ‘ ~ ‘ è più diretto al problema principale. Pubblicherò la sceneggiatura finale e la spiegazione in unaltra risposta. Ma tutto il merito per te: D
- @JohnSiu, sì, luso di find è stato quello che mi è venuto in mente per la prima volta. Potrebbe anche essere utilizzabile, a seconda dellesatta necessità. (o anche meglio, per alcuni usi.)
- @kevinarpe, penso che gli array siano fondamentalmente pensati proprio per questo, e sì,
"${array[@]}"
(con le virgolette! ) è documentato (vedi qui e qui ) per espandersi agli elementi come parole distinte senza dividendoli ulteriormente. - @sixtyfive, beh,
[abc]
è una parte standard dei pattern glob , come?
, non ‘ credo che ‘ sia necessario coprirli tutti qui .
Risposta
Puoi salvarlo come un array invece di una stringa da utilizzare in seguito in molti casi e lascia che il globbing accada quando lo definisci. Nel tuo caso, ad esempio:
k=(~/code/public/*/*.launch) for i in "${k[@]}"; do
o nellesempio successivo, tu “Dovrò eval
alcune 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
Commenti
- Nota come
$exclude
contiene caratteri jolly, tu ‘ d deve disabilitare il globbing prima di utilizzare loperatore split + glob e ripristinarlo per$i/$j
e non utilizzareeval
ma usa"$i"/$j
- Sia tu che ilkkachu date una buona risposta. Tuttavia la sua risposta ha identificato il problema. a lui.
Risposta
La risposta di @ilkkachu ha risolto il problema principale del globbing. Pieno merito a lui.
V1
Tuttavia, a causa del fatto che exclude
contiene voci con e senza carattere jolly (*), inoltre potrebbero non esistere In definitiva, è necessario un controllo aggiuntivo dopo il globbing di $i/$j
. Condivido 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 delloutput
Di seguito è riportato loutput 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 viene mostrato perché la voce di esclusione ($j
) non ha caratteri jolly, $i/$j
diventa 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 visualizzato come voce di esclusione ($j
) contiene carattere jolly ma non ha corrispondenza file / directory, il globbing di $i/$j
restituisce solo la stringa originale.
V2
V2 usa virgolette singole, eval
e shopt -s nullglob
per ottenere un risultato pulito. Nessun controllo finale di file / directory richiede.
#!/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
Commenti
- Un problema è che in
for j in $exclude
, i globi in$exclude
potrebbero essere espansi al momento di tale$exclude
espansione (e chiamandoeval
in cui si stanno chiedendo problemi). ‘ desideri che il globbing sia abilitato perfor i in $dir
efor l in $k
, ma non perfor j in $exclude
. ‘ vuoi unset -f
prima di questultimo e unset +f
per laltro. Più in generale, ‘ vorresti regolare il tuo operatore split + glob prima di usarlo. In ogni caso, non ‘ vuoi split + glob perecho $l
, quindi$l
dovrebbe essere citato lì. - @St é phaneChazelas ti riferisci a v1 o v2? Per la v2, sia
exclude
edirs
sono racchiusi tra virgolette singole (), so no globbing till
eval`. - Il globbing ha luogo su espansione variabile non quotata in contesti elenco , che (lasciando una variabile non quotata) è ciò che noi a volte chiamano loperatore split + glob . ‘ non si occupa di globbing nelle assegnazioni alle variabili scalari.
foo=*
efoo='*'
è lo stesso. Maecho $foo
eecho "$foo"
non lo sono (in shell comebash
, ‘ è stato corretto in shell come zsh, fish o rc, vedi anche il link sopra). Qui fai voglio usare quelloperatore, ma in alcuni punti solo la parte divisa e in altri solo la parte glob. - @St é phaneChazelas Grazie per le informazioni !!! Mi ci è voluto un po di tempo ma ora capisco la preoccupazione. Questo è molto prezioso !! Grazie !!!
Rispondi
Con zsh
:
exclude=" *.launch .classpath .sass-cache Thumbs.db ... " dirs=( ~/code/private/* ~/code/public/* ) for f ($^dirs/${^${=~exclude}}(N)) { echo $f }
${^array}string
deve espandersi come $array[1]string $array[2]string...
. $=var
consiste nelleseguire la suddivisione delle parole sulla variabile (qualcosa che altre shell fanno per impostazione predefinita!), $~var
esegue il globbing sulla variabile (qualcosa di shell anche per impostazione predefinita (quando generalmente non vuoi che lo facciano, avresti dovuto citare $f
sopra in altre shell)).
(N)
è un qualificatore glob che attiva nullglob per ciascuno di quei glob risultanti da quel $^array1/$^array2
espansione. Ciò fa sì che i globi si espandano fino a diventare nulla quando non corrispondono. Ciò accade anche per trasformare un non glob come ~/code/private/foo/Thumbs.db
in uno, il che significa che se quel particolare non esiste, non è incluso.
Commenti
- Questo è davvero carino. Ho provato e funziona. Tuttavia, sembra che zsh sia più sensibile al newline quando utilizzando virgolette singole. Il modo in cui
exclude
è racchiuso influisce sulloutput. - @JohnSiu, oh sì, tu ‘ è corretto. Sembra che lo split + glob e il
$^array
debbano essere eseguiti in due passaggi separati per assicurarsi che gli elementi vuoti vengano scartati (vedi modifica). Sembra un po come un bug inzsh
, ‘ solleverò il problema nella loro mailing list. - Mi viene in mente una v2 per bash, che è più pulito, ma ancora non compatto come il tuo script zsh, lol
Answer
Vecchio post ma sono inciampato qui quindi t ho pensato che questo potrebbe aiutare gli altri 🙂
Cè una funzione kinda-glob () in bash … si chiama compgen -G Emette un valore per riga quindi ha bisogno di un readarray per funzionare.
Prova questo:
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
è una stringa calcolata e ne ho bisogno sta in questo modo fino al ciclo. Si prega di controllare la mia versione lunga.