Come rendere bash glob una variabile stringa?

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

  • Le virgolette singole interrompono linterpolazione della shell in Bash, quindi potresti provare a citare due volte la tua variabile.
  • @ThomasN no, non funziona. k è una stringa calcolata e ne ho bisogno sta in questo modo fino al ciclo. Si prega di controllare la mia versione lunga.
  • @ThomasN Ho aggiornato la versione breve per renderla più chiara.

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 utilizzare eval 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 chiamando eval in cui si stanno chiedendo problemi). ‘ desideri che il globbing sia abilitato per for i in $dir e for l in $k, ma non per for j in $exclude. ‘ vuoi un set -f prima di questultimo e un set +f per laltro. Più in generale, ‘ vorresti regolare il tuo operatore split + glob prima di usarlo. In ogni caso, non ‘ vuoi split + glob per echo $l, quindi $l dovrebbe essere citato lì.
  • @St é phaneChazelas ti riferisci a v1 o v2? Per la v2, sia exclude e dirs 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=* e foo='*' è lo stesso. Ma echo $foo e echo "$foo" non lo sono (in shell come bash, ‘ è 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 in zsh, ‘ 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 

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *