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