Benzer bash işlevi bir kerede nasıl tanımlanır


10

Ben bu işlevleri var ~/.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
}

Gördüğünüz gibi bu işlevler çok benzer. Bu 3 işlevi bir kerede tanımlamak istiyorum. Bunu yapmanın bir yolu var mı?

çevre

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

Yanıtlar:


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[@]}" "$@"
}

İle ilgili genel uyarılar evalgeçerlidir.


for loop?Demek istediğim tarafından yenilmez mi , for loopgenel olarak beyan edilen değişkenler ortadan kalkar - aynı nedenlerle aynı işlevleri beklerim.
Mart'ta mikeserv

Sizi böyle düşündüren nedir? bash -c 'for i in 1; do :; done; echo $i'=> 1. typeAçıkça gösterir fonksiyonlar döngü kapsamı dışında var olduğunu.
Adrian Frühwirth

1
@mikeserv bashDinamik kapsam belirleme özelliğiyle bile, tüm fonksiyonunlocal kapsamı için yerel olan bir değişken olsa da , değişkenler bir döngüden sonra kesinlikle "kaybolmaz". Aslında, burada herhangi bir işlev olmadığından , bu durumda bir değişken tanımlamak bile mümkün değildir . local
Adrian Frühwirth

Sağ - for döngüsü için yerel - yerel olarak kapsamlıdır. Ana döngü kabukları olur olmaz kaybolurlar. Burada olmaz mı?
Mart'ta mikeserv

Hayır, daha önce açıkladığım gibi, kabuk komut dosyalarında "for döngüsü için yerel" diye bir kavram yoktur ve yazım ve yukarıdaki yorumumdaki örnek bunu açıkça göstermektedir.
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."

Önceden değerlendirilmiş tek iş olarak çağrıldığında işleve verilen yukarıdaki irade . source /dev/fd/3, bir parametre almak ve onu hem hedef hem de hedeflendiği işlevin adı olarak önceden değerlendirmektir ._gem_dec()here-document. _gem_dec'sbundle exec

NOTE: . sourcing shell expansions results in twice-evaluated variables - just like eval. It can be risky.

Yukarıdaki durumda, herhangi bir risk olabileceğini düşünmüyorum.

Yukarıdaki kod bloğu bir kopyalanır ise .bashrcdosyaya sadece kabuk fonksiyonları olacak _guard(), _rspec()ve_rake() Giriş sırasında beyan edilmesi, ancak _gem_dec()işlevi de kabuk olarak herhangi bir zamanda yürütme için hazır olacaktır istemi (veya başka türlü) ve bu yüzden yeni templated fonksiyonlar can ne zaman isterseniz:

_gem_dec $new_templated_function_name

@Andrew'a bana gösterdikleri için teşekkürler. for loop.

AMA NASIL?

Alışkanlığı açık 3tutmak için yukarıdaki dosya tanımlayıcıyı kullanıyorum stdin, stdout, and stderr, or <&0 >&1 >&2- yine de burada uyguladığım diğer varsayılan önlemlerin birçoğunda olduğu gibi - sonuçta ortaya çıkan işlev çok basit, gerçekten gerekli değil. Yine de iyi bir uygulamadır. çağrıshift $# , bu gereksiz önlemlerden bir diğeri.

Yine de, bir dosya olarak <inputveya>output ile [optional num]<fileveya [optional num]>fileyeniden yönlendirme belirtildiğinde , çekirdek dosyayı character deviceözel dosyalardan erişilebilen bir dosya tanımlayıcısında okur /dev/fd/[0-9]*. Eğer [optional num]belirleyicisi atlanırsa, daha sonra 0<filegiriş için ve varsayılır 1>fileçıkış için. Bunu düşün:

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

Ve a here-document, yalnızca yaptığımız zaman kod bloğundaki satır içi dosyayı tanımlamanın bir yolu olduğundan:

<<'HEREDOC'
[$CODE]
HEREDOC

Biz de yapabiliriz:

echo '[$CODE]' >/dev/fd/0

Biri ile çok önemli bir ayrım. Bunu yapmazsanız a sonra kabuk kabuk için değerlendirecek gibi:"'\quote'"<<"'\LIMITER"'here-document$expansion

echo "[$CODE]" >/dev/fd/0

Yani, _gem_dec(),3<<-FUNC here-document girişinde bir dosya, bu olsaydı olurdu aynı şekilde değerlendirilir 3<~/some.file dışında biz bırakın çünkü FUNCsınırlayıcı ilk için değerlendirilir tırnak serbest, $expansion.bu konuda önemli şey o girdi, anlamı olmasıdır yalnızca var _gem_dec(),olmakla birlikte, _gem_dec()işlev çalıştırılmadan önce de değerlendirildiği için, kabuğumuzun girdi olarak teslim $expansionsetmeden önce bunu okuması ve değerlendirmesi gerekir.

guard,Örneğin şunu yapalım :

_gem_dec guard

Bu nedenle, önce kabuk girdiyi işlemek zorundadır, yani okuma:

3<<-FUNC
    _${1}() { [ ! -e 'Gemfile' ] && { 
        command $1 "\$@" ; return \$?
        } || bundle exec $1 "\$@"
    }
FUNC

Dosya tanımlayıcı 3'e ve kabuk genişletme için değerlendirilmesine. Şu anda koştuysanız:

cat /dev/fd/3

Veya:

cat <&3

Her ikisi de eşdeğer komut olduğundan * görürsünüz:

_guard() { [ ! -e 'Gemfile' ] && { 
    command guard "$@" ; return $?
    } || bundle exec guard "$@"
}

... daha önce fonksiyondaki herhangi bir kod hiç çalıştırılmaz. Bu fonksiyon en olduğunu <input, sonuçta. Daha fazla örnek için burada farklı bir soruya verdiğim cevaba bakınız .

(* Teknik olarak bu tamamen doğru değil. Daha -dashönce bir lider kullandığım için here-doc limiter, yukarıdakilerin hepsi sola dayalı olacaktı. Ama ilk başta okunabilirlik için -dashkullanabildim , bu yüzden daha önce soymayacağım size okumak için teklif ...)<tab-insert><tab-inserts>

Bununla ilgili en güzel kısım, alıntıdır - '"tırnakların kaldığına ve sadece \tırnakların çıkarıldığına dikkat edin. Muhtemelen bu nedenle diğerlerinden daha fazla bir kabuk iki kez değerlendirmek zorunda kalırsanız $expansion, here-documentçünkü tırnak daha çok daha kolayeval .

Her neyse, şimdi yukarıdaki kod tam olarak fonksiyonun devam etmesini ve girişini kabul etmesini 3<~/heredoc.filebeklemek gibi beslenen bir dosya gibidir _gem_dec()./dev/fd/3 .

_gem_dec()İlk başladığımızda yaptığım şey tüm konumsal parametreleri atmaktır , çünkü bir sonraki adımımız iki kez değerlendirilen bir kabuk genişletmesi ve içerilenlerin $expansionshiçbirinin mevcut $1 $2 $3...parametrelerimden biri olarak yorumlanmasını istemiyorum . Yani ben:

shift $#

shiftpositional parametersbelirttiğiniz kadar atar ve kalanla başlar $1. Yani denilen Ben eğer _gem_dec one two threeistemi de _gem_dec's $1 $2 $3konumsal parametreler olacağını one two threeve toplam akım pozisyonel sayımı ya $#Sonra denilen Eğer 3. olacağını shift 2,değerlerini onevetwo olacağını shifted uzakta, değerini $1değiştirmek istiyorum threeve $#kadar genişleyip 1. Yani shift $#sadece hepsini atar. Bunu yapmak kesinlikle tedbirlidir ve bir süredir bu tür bir şey yaptıktan sonra geliştirdiğim bir alışkanlıktır. Burada (subshell)açıklık uğruna biraz yayılmış durumda:

( 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

Her neyse, bir sonraki adım sihrin gerçekleştiği yerdir. Eğer . ~/some.shkabuk komut isteminde, bildirilen tüm işlevler ve ortam değişkenleri ~/some.shkabuk komut isteminizde çağrılabilir olur. Aynı biz burada hariç doğrudur bizim dosya tanımlayıcı için özel dosya veya - bizim nerede olduğu ve bizim fonksiyonunu ilan ettik - in-line dosya pathed edilmiştir. Ve işte böyle çalışır.. sourcecharacter device. /dev/fd/3here-document

_guard

Şimdi _guardfonksiyonunuz ne gerekiyorsa yapar.

Zeyilname:

Konumlarınızı kaydetmenin harika bir yolu:

f() { . /dev/fd/3
} 3<<-ARGS
    args='${args:-"$@"}'
ARGS

DÜZENLE:

Bu soruyu ilk cevapladığımda function(), mevcut kabuk $ENVütülemesinde devam edecek diğer işlevleri beyan edebilecek bir mermi ilan etme sorununa, askerin söz konusu kalıcı işlevlerle ne yapacağından daha çok odaklandım . O zamandan beri 3<<-FUNC, şu anda formda yer alan benim önerdiğim çözümün farkına vardım :

3<<-FUNC
    _${1}() { 
        if [ -e 'Gemfile' ]; then
            bundle exec $1 "\$@"
        else 
            command _${1} "\$@"
    }
FUNC

Özellikle gelen bildirime işlevin adını değiştirdi, çünkü asker için beklendiği gibi muhtemel çalışmış olmaz $1için _${1}hangi gibi denilen eğer, _gem_dec guardörneğin, neden olacaktır _gem_decadında bir işlevi bildirerek _guardsadece karşıt olarak guard.

Not: Bu tür davranışlar benim için bir alışkanlık meselesidir - Tipik olarak kabuk işlevlerinin kabuküzerine girmelerini önlemek içinkabuk işlevlerinin yalnızca kendilerine aitolması gerektiği varsayımıyla çalışırım._namespacenamespacecommands düzgün.

Bununla birlikte, bu, istemcinin commandçağrı yapmak için kullanıldığından anlaşıldığı gibi evrensel bir alışkanlık değildir $1.

Daha fazla inceleme beni aşağıdakilere inandırıyor:

Asker adında kabuk işlevleri istiyor guard, rspec, or rake, çağrıldığında, yeniden bir derleme edeceği rubyaynı adı taşıyan işlevi ifdosya Gemfilevar $PATH VEYA if Gemfile kabuk işlevi yürütmek gerektiğini yoksa rubyaynı adı taşıyan işlevi.

Ben de değişmiş olduğundan bu daha önce çalışmış olmazdı $1tarafından talep commandokumak için:

command _${1}

Bu ruby, kabuk işlevinin şu şekilde derlendiği işlevin yürütülmesiyle sonuçlanmazdı :

bundle exec $1

Umarım (sonunda yaptığım gibi) askerin sadece commanddolaylı olarak belirtmek için kullandığını görüyorsunuz namespaceçünkü commandyürütülebilir bir dosyayı çağırmayı tercih edecek$PATH aynı ada sahip bir kabuk işlevi üzerinde .

Benim analiz doğruysa (umarım asker onaylar) o zaman bu:

_${1}() { [ ! -e 'Gemfile' ] && { 
    command $1 "\$@" ; return \$?
    } || bundle exec $1 "\$@"
}

guardKomut isteminde çağırmanın yalnızca$PATH adlandırılmış bir yürütülebilir dosyayı yürütmeye çalışması dışında bu koşulları daha iyi karşılaması guardgerekirken _guard, istemde çağrılması Gemfile'svarlığını kontrol eder ve buna göre derlenir veya çalıştırılabilir guarddosyayı yürütür $PATH. Bu şekilde namespacekorunur ve en azından algıladığım gibi, sorucunun niyeti hala yerine getirilir.

Aslında, bizim kabuk işlevi küstah _${1}()ve yürütülebilir ${PATH}/${1}olan sadece iki bizim kabuk ya bir çağrı yorumlayabilir yolları $1veya _${1}sonra kullanılmasıcommand hiç fonksiyonunda artık tamamen gereksiz yapılır. Yine de, aynı hatayı iki kez üst üste yapmak istemediğim için kalmasına izin verdim.

Eğer bu asker için kabul edilemezse ve o zaman _tamamen ortadan kaldırmayı tercih ederse , mevcut haliyle, çıktıyı düzenlemek, _underscoreanladığım kadarıyla gereksinimlerini karşılamak için tüm ihtiyaç duyulan şey olmalıdır.

Bu değişikliğin yanı sıra , orijinal sözdiziminden ziyade kısa devre koşullarını kullanma &&ve / veya|| kabuklama işlevini de düzenledim . Bu şekilde ifadesi yalnızca değerlendirilir hiç olmadığını değil . Bu değişiklik , ifadenin olayda çalışmadığından emin olmak için ancak eklemenin 0'dan başka bir şey döndürmesini sağlamak için eklenmesini gerektirir .if/thencommandGemfile$PATHreturn $?bundleGemfileruby $1

Son olarak, bu çözümün sadece taşınabilir kabuk yapılarını uyguladığını not etmeliyim. Başka bir deyişle, bu POSIX uyumluluğunu iddia eden herhangi bir kabukta aynı sonuçları üretmelidir. Beni her POSIX uyumlu sistem işlemesi gerekir iddia için, tabii ki, saçma olurdu iken ruby bundleyönergesi ne olursa olsun çağıran kabuk olup olmadığı aynı davranması gerektiği üzerine en az kabuk zorunluluklar arayarak shya dash. Ayrıca yukarıdakiler her ikisinde de beklendiği gibi çalışacaktır ( shoptsyine de en azından yarım aklı başında olduğu varsayılır ) bashve zsh.


Kodunuzu girip ~/.bashrcçağırıyorum . ~/.bashrc, sonra bu üç işlev yürütülür. Belki de davranış çevreye göre değişir, bu yüzden soruyu çevreme ekledim. Ayrıca son hattın neden _guard ; _rspec ; _rakegerekli olduğunu anlayamadım . Aradım shiftve dosya tanımlayıcı, bunlar şu anki anlayışımın ötesinde görünüyor.
ironsand

Ben sadece orada çağrılabilir olduğunu göstermek için koydu. Üzgünüm - Şimdi bir yankı koydum. Böylece onları gösterdiğiniz gibi işlev olarak adlandırabilirsiniz.
Mart'ta mikeserv

@Tetsu - şimdi daha mantıklı mı?
2014'te

Cevabınızı 3 kez okudum, ama dürüstçe açıklamayı anlamak için daha fazla bilgiye ihtiyacım olduğunu söylüyorum. Sana çok minnettar kalsam da, daha fazla deneyim kazandığımda tekrar okuyacağım.
ironsand

@Tetsu Belki şimdi daha net ...? Sanırım daha önce yaptığım bir hatayı fark ettim ve şimdi düzelttim. Lütfen bana bildirin, eğer istersen.
2014'te

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
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.