_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."
Powyższa wola, . source /dev/fd/3która jest wprowadzana do _gem_dec()funkcji za każdym razem, gdy jest wywoływana jako here-document. _gem_dec'szadanie wstępnie ocenione, ma otrzymać jeden parametr i wstępnie ocenić go zarówno jako bundle execcel, jak i nazwę funkcji, w której jest on docelowy.
NOTE: . sourcing shell expansions results in twice-evaluated variables - just like eval. It can be risky.
W powyższym przypadku nie sądzę, aby istniało jakiekolwiek ryzyko.
Jeżeli powyższy kod blok jest kopiowany do .bashrcpliku, nie tylko funkcje powłoki _guard(), _rspec()i_rake() być zadeklarowane podczas logowania, ale _gem_dec()funkcja będzie dostępna do realizacji w dowolnym momencie w swojej skorupie prompt (lub inaczej) i tak nowy szablonowe funkcje mogą zadeklaruj, kiedy tylko chcesz:
_gem_dec $new_templated_function_name
I dzięki @Andrew za pokazanie mi, że nie zostaną zjedzeni przez for loop.
ALE JAK?
Używam 3powyższego deskryptora pliku, aby trzymać stdin, stdout, and stderr, or <&0 >&1 >&2się z dala od przyzwyczajenia - chociaż, podobnie jak w przypadku kilku innych domyślnych środków ostrożności, które tu wdrażam - ponieważ wynikowa funkcja jest tak prosta, że tak naprawdę nie jest konieczna. Jest to jednak dobra praktyka. Dzwonienie shift $#to kolejna z tych niepotrzebnych środków ostrożności.
Mimo to, gdy plik jest określony jako <inputalbo>output z [optional num]<filelub [optional num]>fileprzekierowanie jądro odczytuje go w deskryptorze pliku, które mogą być dostępne za pośrednictwem character devicespecjalnych plików /dev/fd/[0-9]*. Jeśli [optional num]specyfikator zostanie pominięty, wówczas 0<fileprzyjmuje się dla danych wejściowych i 1>filewyjściowych. Rozważ to:
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
A ponieważ a here-documentjest jedynie sposobem na opisanie pliku wewnątrz bloku kodu, kiedy:
<<'HEREDOC'
[$CODE]
HEREDOC
Równie dobrze moglibyśmy:
echo '[$CODE]' >/dev/fd/0
Z jednym bardzo ważnym rozróżnieniem. Jeśli nie tematyce następnie powłoka oceni go do muszli , takich jak:"'\quote'"<<"'\LIMITER"'here-document$expansion
echo "[$CODE]" >/dev/fd/0
Tak więc, dla _gem_dec(),3<<-FUNC here-document jest oceniany jako plik na wejściu, tak samo jak byłoby, gdyby to było 3<~/some.file wyjątkiem , że ponieważ mamy opuścić FUNCogranicznik darmo cytatów, jest to pierwszy oceniano $expansion.Ważną rzeczą jest to, że jest to wejście, znaczenie istnieje _gem_dec(),tylko dlatego, że jest również analizowany przed uruchomieniem _gem_dec()funkcji, ponieważ nasza powłoka musi ją odczytać i ocenić $expansionsprzed przekazaniem jej jako danych wejściowych.
Zróbmy guard,na przykład:
_gem_dec guard
Więc najpierw powłoka musi obsłużyć dane wejściowe, co oznacza odczyt:
3<<-FUNC
_${1}() { [ ! -e 'Gemfile' ] && {
command $1 "\$@" ; return \$?
} || bundle exec $1 "\$@"
}
FUNC
W deskryptor pliku 3 i ocenianie go pod kątem rozszerzenia powłoki. Jeśli w tej chwili prowadziłeś:
cat /dev/fd/3
Lub:
cat <&3
Ponieważ oba są równoważnymi poleceniami, zobaczysz *:
_guard() { [ ! -e 'Gemfile' ] && {
command guard "$@" ; return $?
} || bundle exec guard "$@"
}
... przedtem w ogóle wykonuje się kod w funkcji. Jest to funkcja użytkownika <input, po wszystkim. Aby uzyskać więcej przykładów, zobacz moją odpowiedź na inne pytanie tutaj .
(* Technicznie nie jest to do końca prawdą. Ponieważ używam wiodącego -dashprzed here-doc limiterpowyższym, powyższe byłoby usprawiedliwione do lewej strony. Ale użyłem -dashtak, żebym mógł przede wszystkim <tab-insert>dla czytelności, więc nie zamierzam usuwać <tab-inserts>wcześniej oferując ci do przeczytania ...)
Najmilszą częścią tego jest cytowanie - zauważ, że '"cytaty pozostały i tylko \cytaty zostały usunięte. Prawdopodobnie z tego powodu bardziej niż jakiegokolwiek innego, jeśli będziesz musiał dwukrotnie ocenić powłokę $expansion, polecę to, here-documentponieważ cytaty są znacznie łatwiejsze niż eval.
W każdym razie, teraz powyższy kod jest dokładnie tak, jak plik wprowadzony, jak 3<~/heredoc.filetylko czekanie na uruchomienie _gem_dec()funkcji i zaakceptowanie jej danych wejściowych /dev/fd/3.
Więc kiedy zaczynamy, _gem_dec()najpierw rzucam wszystkie parametry pozycyjne, ponieważ naszym następnym krokiem jest podwójnie ocenione rozszerzenie powłoki i nie chcę, aby którykolwiek z zawartych elementów $expansionsbył interpretowany jako jeden z moich bieżących $1 $2 $3...parametrów. Więc ja:
shift $#
shiftodrzuca tyle, positional parametersile określisz i zaczyna od $1tego, co pozostało. Tak więc, gdybym wywołał _gem_dec one two threew linii poleceń _gem_dec's $1 $2 $3parametry pozycyjne byłyby, one two threea całkowita bieżąca liczba pozycyjna, lub $#wynosiłaby 3. Gdybym wtedy wywołał shift 2,wartości oneitwo zostałby shiftusunięty , wartość $1zmieniłaby się na threei $#rozszerzyłaby się do 1. Więc shift $#po prostu wyrzuca je wszystkie. Robienie tego jest ściśle zapobiegawcze i jest tylko nawykiem, który rozwinąłem po zrobieniu tego rodzaju rzeczy przez jakiś czas. Oto (subshell)trochę dla jasności:
( 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
W każdym razie następnym krokiem jest magia. Jeśli pojawi . ~/some.shsię monit powłoki, wszystkie funkcje i zmienne środowiskowe zadeklarowane w programie ~/some.shbędą mogły być wywoływane po zachęcie powłoki. Tak samo jest tutaj, z wyjątkiem my plik specjalny dla naszego deskryptor pliku, lub - co jest gdzie nasz plik w linii został pathed - i mamy zadeklarowane naszą funkcję. I tak to działa.. sourcecharacter device. /dev/fd/3here-document
_guard
Teraz robi wszystko, co _guardpowinna zrobić twoja funkcja.
Uzupełnienie:
Świetny sposób na zapisanie pozycji:
f() { . /dev/fd/3
} 3<<-ARGS
args='${args:-"$@"}'
ARGS
EDYTOWAĆ:
Kiedy po raz pierwszy odpowiedziałem na to pytanie, bardziej skupiłem się na problemie zadeklarowania powłoki function()zdolnej do zadeklarowania innych funkcji, które pozostaną w bieżącym $ENVetapie prasowania powłoki, niż na tym, co pytający zrobiłby z tymi stałymi funkcjami. Od tego czasu zdałem sobie sprawę, że moje pierwotnie zaproponowane rozwiązanie, w którym 3<<-FUNCprzyjęło formę:
3<<-FUNC
_${1}() {
if [ -e 'Gemfile' ]; then
bundle exec $1 "\$@"
else
command _${1} "\$@"
}
FUNC
Nie będzie prawdopodobnie pracował zgodnie z oczekiwaniami do pytającego, bo specjalnie zmienił nazwę deklaratywnego funkcja jest od $1celu _${1}, który, jeśli nazywa się tak jak _gem_dec guardna przykład, spowodowałoby _gem_decdeklarowania funkcji o nazwie _guarda nie tylko guard.
Uwaga: Takie zachowanie jest dla mnie kwestią przyzwyczajenia - zazwyczaj działam z założeniem, że funkcje powłoki powinny zajmować tylko własne,_namespaceaby uniknąć włamania się donamespacepowłokicommands.
Nie jest to jednak powszechny nawyk, o czym świadczy użycie commandwezwania przez pytającego $1.
Dalsze badanie prowadzi mnie do wniosku, że:
Pytający chce, aby funkcje powłoki o nazwie, guard, rspec, or rakektóre po wywołaniu skompilują na nowo rubyfunkcję o tej samej nazwie, ifco plik Gemfileistnieje w $PATH OR
if Gemfile , nie istnieje, funkcja powłoki powinna wykonać rubyfunkcję o tej samej nazwie.
Nie działałoby to wcześniej, ponieważ zmieniłem również $1wezwanie commanddo przeczytania:
command _${1}
Co nie spowodowałoby wykonania rubyfunkcji skompilowanej przez funkcję powłoki jako:
bundle exec $1
Mam nadzieję, że zobaczysz (jak ostatecznie to zrobiłem) , że wydaje się, że pytający commandw ogóle używa tylko pośrednio, namespaceponieważ commandwoli wywołać plik wykonywalny w $PATHfunkcji powłoki o tej samej nazwie.
Jeśli moja analiza jest poprawna (jak mam nadzieję pytający to potwierdzi), to:
_${1}() { [ ! -e 'Gemfile' ] && {
command $1 "\$@" ; return \$?
} || bundle exec $1 "\$@"
}
Powinien lepiej spełniać te warunki, z wyjątkiem tego, że wywołanie guardw wierszu będzie tylko próbowało wykonać plik wykonywalny w $PATHnazwie, guardpodczas gdy wywołanie _guardw wierszu sprawdzi, czy Gemfile'sistnieje i odpowiednio się skompiluje lub uruchomi guardplik wykonywalny w $PATH. W ten sposób namespacejest chroniony i, przynajmniej tak, jak go postrzegam, zamiary pytającego są nadal spełnione.
W rzeczywistości, zakładając, że nasza funkcja powłoki _${1}()i plik wykonywalny ${PATH}/${1}to jedyne dwa sposoby, w jakie nasza powłoka może interpretować wywołanie albo, $1albo _${1}użycie commandfunkcji w ogóle jest teraz całkowicie zbędne. Mimo to pozwoliłem temu pozostać, ponieważ nie lubię popełniać tego samego błędu dwa razy ... w każdym razie z rzędu.
Jeśli jest to nie do przyjęcia dla pytającego, a on / ona wolałaby _całkowicie się pozbyć , to w obecnej formie edycja _underscorewyjścia powinna być wszystkim, co pytający musi zrobić, aby spełnić jego / jej wymagania, tak jak je rozumiem.
Oprócz tej zmiany zredagowałem również funkcję, aby używać &&i / lub|| warunek zwarcia powłoki zamiast oryginalnej if/thenskładni. W ten sposób commandinstrukcja jest oceniana w ogóle tylko wtedy, gdy jej Gemfilenie ma $PATH. Ta modyfikacja wymaga jednak dodania, return $?aby upewnić się, że bundleinstrukcja nie zostanie uruchomiona, jeśli zdarzenie Gemfilenie istnieje, ale ruby $1funkcja zwraca wartość inną niż 0.
Na koniec należy zauważyć, że to rozwiązanie implementuje tylko przenośne konstrukcje powłoki. Innymi słowy, powinno to dawać identyczne wyniki w dowolnej powłoce, która twierdzi, że jest zgodna z POSIX. Choć oczywiście byłoby dla mnie nonsensem twierdzenie, że każdy system zgodny z POSIX musi obsługiwać ruby bundledyrektywę, przynajmniej imperatywne powłoki wywołujące go powinny zachowywać się tak samo, niezależnie od tego, czy wywołująca powłoka jest, shczy też dash. Również powyższe prace będą zgodnie z oczekiwaniami (zakładając, przynajmniej w połowie-sane shoptsw każdym razie) w obu bashi zsh.
for loop?oznacza to, że to znaczy, że zmienne zadeklarowane wfor loopzasadzie znikają - oczekiwałbym tego samego od funkcji z tych samych powodów.