Jak zdefiniować podobną funkcję bash na raz


10

Mam te funkcje w ~/.bashrc:

function guard() {
    if [ -e 'Gemfile' ]; then
    bundle exec guard "$@"
    else
    command guard "$@"
    fi
}
function rspec() {
    if [ -e 'Gemfile' ]; then
    bundle exec rspec "$@"
    else
    command rspec "$@"
    fi
}
function rake() {
    if [ -e 'Gemfile' ]; then
        bundle exec rake "$@"
    else
        command rake "$@"
    fi
}

Jak widzisz, te funkcje są bardzo podobne. Chcę zdefiniować te 3 funkcje jednocześnie. Czy istnieje sposób, aby to zrobić?

środowisko

bash --version
GNU bash, version 3.2.51(1)-release (x86_64-apple-darwin13)

Odpowiedzi:


8
$ cat t.sh
#!/bin/bash

for func in guard rspec rake; do
        eval "
        ${func}() {
                local foo=(command ${func})
                [ -e 'Gemfile' ] && foo=(bundle exec ${func})
                \"\${foo[@]}\" \"\$@\"
        }
        "
done

type guard rspec rake

.

$ ./t.sh
guard is a function
guard ()
{
    local foo=(command guard);
    [ -e 'Gemfile' ] && foo=(bundle exec guard);
    "${foo[@]}" "$@"
}
rspec is a function
rspec ()
{
    local foo=(command rspec);
    [ -e 'Gemfile' ] && foo=(bundle exec rspec);
    "${foo[@]}" "$@"
}
rake is a function
rake ()
{
    local foo=(command rake);
    [ -e 'Gemfile' ] && foo=(bundle exec rake);
    "${foo[@]}" "$@"
}

Obowiązują zwykłe przestrogi dotyczące eval.


Nie for loop?oznacza to, że to znaczy, że zmienne zadeklarowane w for loopzasadzie znikają - oczekiwałbym tego samego od funkcji z tych samych powodów.
— mikeserv

Dlaczego tak sądzisz? bash -c 'for i in 1; do :; done; echo $i'=> 1. Te typewyraźnie pokazuje, że istnieją funkcje poza zakresem pętli.
— Adrian Frühwirth

1
@ mikeserv Nawet przy bashdynamicznym określaniu zakresu wszystko, co możesz uzyskać, to localzmienna lokalna dla zakresu całej funkcji , zmienne zdecydowanie nie „znikają” po pętli. W rzeczywistości, ponieważ nie ma tu żadnej funkcji, zdefiniowanie localzmiennej w tym przypadku nie jest nawet możliwe .
— Adrian Frühwirth

Po prawej - lokalnie do pętli for - mają zasięg lokalny. Znikają, gdy tylko powłoka nadrzędnej pętli przestanie działać. Czy to się tu nie zdarza?
— mikeserv

Nie, jak właśnie wyjaśniłem, nie ma takiej koncepcji jak „lokalna dla pętli for” w skryptach powłoki, a mój post i przykład w moim komentarzu powyżej wyraźnie to pokazują.
— Adrian Frühwirth

7
_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.


Wstawiam twój kod ~/.bashrci wywołuję . ~/.bashrc, a następnie te trzy funkcje są wykonywane. Może zachowanie różni się w zależności od środowiska, więc do pytania dodałem swoje środowisko. Poza tym nie mogłem zrozumieć, dlaczego _guard ; _rspec ; _rakepotrzebna jest ostatnia linia . Szukałem shifti deskryptor pliku, wygląda na to, że są one poza moim obecnym zrozumieniem.
— ironsand

Po prostu umieściłem to, aby pokazać, że można je wywołać. Przepraszam - włączyłem echo. Możesz więc wywoływać je jako funkcje, jak wykazałeś.
— mikeserv

@Tetsu - czy ma to teraz lepszy sens?
— mikeserv

Przeczytałem twoją odpowiedź 3 razy, ale szczerze mówiąc, potrzebuję więcej wiedzy, aby zrozumieć wyjaśnienie. Mimo że jestem ci bardzo wdzięczny, przeczytam ją ponownie, gdy zdobędę więcej doświadczenia.
— ironsand

@Tetsu Być może teraz jest to jaśniejsze ...? Myślę, że zdałem sobie sprawę, a teraz poprawiłem, błąd, który popełniłem wcześniej. Daj mi znać, jeśli chcesz.
— mikeserv

2
function threeinone () {
    local var="$1"
    if [ $# -ne 1 ]; then
        return 1
    fi
    if ! [ "$1" = "guard" -o "$1" = "rspec" -o "$1" = "rake" ]; then
        return 1
    fi
    shift
    if [ -e 'Gemfile' ]; then
        bundle exec "$var" "$@"
    else
        command "$var" "$@"
    fi
}

threeinone guard
threeinone rspec
threeinone rake
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.