_gem_dec() { shift $# ; . /dev/fd/3
} 3<<-FUNC
_${1}() { [ ! -e 'Gemfile' ] && {
command $1 "\$@" ; return \$?
} || bundle exec $1 "\$@"
}
FUNC
for func in guard rspec rake ; do _gem_dec $func ; done
echo "_guard ; _rspec ; _rake are all functions now."
La volontà precedente . source /dev/fd/3che viene inserita nella _gem_dec()funzione ogni volta che viene chiamata come here-document. _gem_dec'sunico lavoro pre-valutato è quella di ricevere un parametro e pre-valutare sia come bundle exectarget sia come nome della funzione in cui è targetizzato.
NOTE: . sourcing shell expansions results in twice-evaluated variables - just like eval. It can be risky.
Nel caso precedente, tuttavia, non penso che ci possa essere alcun rischio.
Se il codice-blocco sopra viene copiato in un .bashrcfile, non solo i le funzioni di shell _guard(), _rspec()e_rake() essere dichiarate al login, ma la _gem_dec()funzione di saranno disponibili anche per l'esecuzione in qualsiasi momento presso la shell pronta (o altro) e così le funzioni di nuovo templated può essere dichiarato quando vuoi con solo:
_gem_dec $new_templated_function_name
E grazie a @Andrew per avermi mostrato questi non sarebbero stati mangiati da un for loop.
MA COME?
Uso il 3descrittore di file sopra per tenermi stdin, stdout, and stderr, or <&0 >&1 >&2aperto per abitudine, anche se, come nel caso di alcune delle altre precauzioni predefinite che implemento qui, perché la funzione risultante è così semplice, non è davvero necessario. È buona pratica, comunque. Chiamare shift $#è un'altra di quelle precauzioni inutili.
Tuttavia, quando un file viene specificato come <inputo>output con [optional num]<fileo [optional num]>filereindirizzamento, il kernel lo legge in un descrittore di file, al quale è possibile accedere tramite i character devicefile speciali in /dev/fd/[0-9]*. Se lo [optional num]specificatore viene omesso, 0<fileviene assunto per l'input e 1>fileper l'output. Considera questo:
l='line %d\n' ; printf "$l" 1 2 3 4 5 6 >/dev/fd/1
> line 1
> line 2
> line 3
> line 4
> line 5
> line 6
( printf "$l" 4 5 6 >/dev/fd/3 ; printf "$l" 1 2 3 ) >/tmp/sample 3>/tmp/sample2
( cat /tmp/sample2 ) </tmp/sample
> line 4
> line 5
> line 6
( cat /dev/fd/0 ) </tmp/sample
> line 1
> line 2
> line 3
( cat /dev/fd/3 ) </tmp/sample 3</tmp/sample2
> line 4
> line 5
> line 6
E poiché a here-documentè solo un mezzo per descrivere un file inline all'interno di un blocco di codice, quando lo facciamo:
<<'HEREDOC'
[$CODE]
HEREDOC
Potremmo anche fare:
echo '[$CODE]' >/dev/fd/0
Con una distinzione molto importante . Se non "'\quote'"l' <<"'\LIMITER"'una here-documentpoi il guscio valuterà per shell $expansioncome:
echo "[$CODE]" >/dev/fd/0
Così, per _gem_dec(), la 3<<-FUNC here-documentviene valutata come un file in ingresso, lo stesso sarebbe se fosse 3<~/some.file tranne che perché lasciamo il FUNClimitatore libera di citazioni, viene prima valutato per $expansion.La cosa importante di questo è che si tratta di ingresso, significato esiste solo per _gem_dec(),ma che viene anche valutato prima dell'esecuzione della _gem_dec()funzione perché la nostra shell deve leggerla e valutarla $expansionsprima di consegnarla come input.
Facciamo guard,ad esempio:
_gem_dec guard
Quindi prima la shell deve gestire l'input, il che significa leggere:
3<<-FUNC
_${1}() { [ ! -e 'Gemfile' ] && {
command $1 "\$@" ; return \$?
} || bundle exec $1 "\$@"
}
FUNC
Nel descrittore di file 3 e valutandolo per l'espansione della shell. Se in questo momento hai eseguito:
cat /dev/fd/3
O:
cat <&3
Dato che sono entrambi comandi equivalenti che vedresti *:
_guard() { [ ! -e 'Gemfile' ] && {
command guard "$@" ; return $?
} || bundle exec guard "$@"
}
... mai prima d'ora qualsiasi codice nella funzione viene eseguito. Questa è la funzione <input, dopo tutto. Per altri esempi, vedi la mia risposta a una domanda diversa qui .
(* Tecnicamente questo non è del tutto vero. Dato che uso un comando -dashprima di here-doc limiter, quanto sopra sarebbe tutto giustificato a sinistra. Ma ho usato il -dashmodo in cui potrei <tab-insert>per leggibilità in primo luogo, quindi non ho intenzione di eliminare il <tab-inserts>prima offrendogli di leggere ...)
La parte più bella di questo è la citazione - nota che le '"virgolette rimangono e solo le \virgolette sono state rimosse. È probabilmente per questo motivo più di ogni altro che se dovessi valutare due volte una shell $expansionti consiglierò here-documentperché le virgolette sono molto più facili di eval.
Comunque, ora il codice sopra è esattamente come un file inserito come 3<~/heredoc.filesolo aspettando che la _gem_dec()funzione inizi e accetti il suo input /dev/fd/3.
Quindi quando iniziamo _gem_dec()la prima cosa che faccio è lanciare tutti i parametri posizionali, perché il nostro prossimo passo è un'espansione della shell valutata due volte e non voglio che nessuno dei contenuti $expansionssia interpretato come uno dei miei $1 $2 $3...parametri attuali . Così io:
shift $#
shiftscarta quante positional parametersne specifichi e parte da $1ciò che resta. Quindi, se ho chiamato _gem_dec one two threeAl prompt di _gem_dec's $1 $2 $3parametri posizionali sarei one two threee il conteggio posizionale corrente totale, o $#sarei 3. Se Ho poi chiamato shift 2,i valori di oneetwo sarei shiftEd via, il valore di $1cambierebbe per threee $#si espanderebbe a 1. Quindi, shift $#solo li butta via tutti. Fare questo è strettamente precauzionale ed è solo un'abitudine che ho sviluppato dopo aver fatto questo tipo di cose per un po '. Eccolo (subshell)un po 'steso per amor di chiarezza:
( set -- one two three ; echo "$1 $2 $3" ; echo $# )
> one two three
> 3
( set -- one two three ; shift 2 ; echo "$1 $2 $3" ; echo $# )
> three
> 1
( set -- one two three ; shift $# ; echo "$1 $2 $3" ; echo $# )
>
> 0
Ad ogni modo, il prossimo passo è dove avviene la magia. Se . ~/some.shal prompt della shell tutte le funzioni e le variabili di ambiente dichiarate in ~/some.shsarebbero richiamabili al prompt della shell. Lo stesso vale qui, tranne che per . source il character devicefile speciale per il nostro descrittore di file o . /dev/fd/3- dove here-documentè stato percorso il nostro file in linea - e abbiamo dichiarato la nostra funzione. Ed è così che funziona.
_guard
Ora fa qualunque cosa la tua _guardfunzione dovrebbe fare.
Addendum:
Un ottimo modo per dire salva i tuoi posizionali:
f() { . /dev/fd/3
} 3<<-ARGS
args='${args:-"$@"}'
ARGS
MODIFICARE:
Quando ho risposto a questa domanda mi sono concentrato di più sul problema di dichiarare un guscio function()in grado di dichiarare altre funzioni che potrebbero persistere nella shell corrente $ENVironment che ho fatto su ciò che il richiedente avrebbe fatto con detto funzioni persistenti. Da allora mi sono reso conto che la mia soluzione originariamente offerta in cui ha 3<<-FUNCpreso la forma:
3<<-FUNC
_${1}() {
if [ -e 'Gemfile' ]; then
bundle exec $1 "\$@"
else
command _${1} "\$@"
}
FUNC
Probabilmente non hanno funzionato come previsto per il richiedente perché ho specificamente alterato il nome della funzione dichiarativa da $1a _${1}che, se chiamato come _gem_dec guardper esempio, porterebbe a _gem_decdichiarare una funzione chiamata _guardin contrasto con appena guard.
Nota: un simile comportamento è per me una questione di abitudine - di solito opero partendo dal presupposto che le funzioni della shell debbano occupare solo le proprie,_namespaceal fine di evitare la loro intrusione nellaproprianamespaceshellcommands.
Questa non è un'abitudine universale, tuttavia, come è dimostrato dall'uso da parte del richiedente di commandfare appello $1.
Un ulteriore esame mi porta a credere a quanto segue:
L'asker desidera che le funzioni della shell vengano denominate guard, rspec, or rakeche, quando viene chiamato, compilerà nuovamente una rubyfunzione con lo stesso nome in cui ifil file Gemfileesiste $PATH O
if Gemfile non esiste, la funzione di shell deve eseguire la rubyfunzione con lo stesso nome.
Questo non avrebbe funzionato in precedenza perché ho anche modificato il $1chiamato da commandleggere:
command _${1}
Il che non avrebbe comportato l'esecuzione della rubyfunzione che la funzione shell ha compilato come:
bundle exec $1
Spero che tu possa vedere (come alla fine ho fatto) che sembra che il richiedente stia usando solo commandper specificare indirettamente namespaceperché commandpreferirà chiamare un file eseguibile in $PATHuna funzione shell con lo stesso nome.
Se la mia analisi è corretta (come spero che il richiedente confermerà), allora questo:
_${1}() { [ ! -e 'Gemfile' ] && {
command $1 "\$@" ; return \$?
} || bundle exec $1 "\$@"
}
Dovrebbe soddisfare meglio tali condizioni con l'eccezione che la chiamata guardal prompt tenterà solo di eseguire un file eseguibile in $PATHnome guardmentre la chiamata _guardal prompt verificherà l' Gemfile'sesistenza e compilerà di conseguenza o eseguirà l' guardeseguibile in $PATH. In questo modo namespaceè protetto e, almeno per come lo percepisco, l'intento del richiedente è ancora soddisfatto.
In realtà, presumere che la nostra funzione shell _${1}()e l'eseguibile ${PATH}/${1}siano gli unici due modi in cui la nostra shell potrebbe interpretare una chiamata a uno $1o _${1}poi l'uso della commandfunzione è ora completamente ridondante. Tuttavia, l'ho lasciato rimanere perché non mi piace fare lo stesso errore due volte ... di fila comunque.
Se questo è inaccettabile per il richiedente e lui / lei preferirebbe eliminare del _tutto allora, nella sua forma attuale, la modifica del _underscoreout dovrebbe essere tutto ciò che il richiedente deve fare per soddisfare i suoi requisiti come li capisco.
A parte questo cambiamento, ho anche modificato la funzione per usare &&e / o|| condizionare i cortocircuiti della shell piuttosto che la if/thensintassi originale . In questo modo l' commandistruzione viene valutata solo a tutti , se Gemfilenon è in $PATH. Questa modifica richiede tuttavia l'aggiunta di return $?per garantire che l' bundleistruzione non venga eseguita se l'evento Gemfilenon esiste ma la ruby $1funzione restituisce qualcosa di diverso da 0.
Infine, dovrei notare che questa soluzione implementa solo costrutti di shell portatili. In altre parole, ciò dovrebbe produrre risultati identici in qualsiasi shell che rivendichi la compatibilità POSIX. Mentre, naturalmente, sarebbe una sciocchezza per me affermare che ogni sistema compatibile con POSIX deve gestire la ruby bundledirettiva, almeno gli imperativi della shell che la invocano dovrebbero comportarsi allo stesso modo indipendentemente dal fatto che la shell chiamante sia sho dash. Anche quanto sopra funzionerà come previsto (presumendo comunque almeno a metà sano di mente shopts) in entrambi bashe zsh.
for loop?, le variabili dichiarate infor loopgenere scompaiono - mi aspetto la stessa funzione per le stesse ragioni.