Zsh içinde kendi shell fonksiyonunu nasıl tanımlayabilir ve yükleyebilirsin?


54

Kendi kabuk işlevlerimi tanımlamak ve çalıştırmak için zsh olarak zorlanıyorum. Resmi dokümantasyondaki talimatları izledim ve ilk önce basit örneklerle çalıştım, ancak çalışmasını sağlayamadım.

Bir klasörüm var:

~/.my_zsh_functions

Bu klasörde adlı bir dosya var functions_1olan rwxkullanıcı izinleri. Bu dosyada tanımlanmış aşağıdaki kabuk işlevi var:

my_function () {
echo "Hello world";
}

Ben tanımlanan FPATHklasörün yolunu içerecek şekilde ~/.my_zsh_functions:

export FPATH=~/.my_zsh_functions:$FPATH

Klasörün .my_zsh_functionsişlevler yolunda echo $FPATHveya ile olduğunu onaylayabilirim.echo $fpath

Ancak, o zaman kabuktan aşağıdakileri deneyin:

> autoload my_function
> my_function

Alırım:

zsh: my_test_function: işlev tanımı dosyası bulunamadı

Aramak için yapmam gereken başka bir şey var mı my_function?

Güncelleme:

Şimdiye kadar verilen cevaplar, dosyayı zsh işlevleriyle almayı önerir. Bu mantıklı, ama biraz kafam karıştı. Bu dosyaların nerede olduğunu bilmemeli FPATHmi? O zaman amacı nedir autoload?


$ ZDOTDIR’ın doğru tanımlanmış olduğundan emin olun. zsh.sourceforge.net/Intro/intro_3.html
ramonovski

2
$ ZDOTDIR'ın değeri bu sorunla ilgili değil. Değişken, zsh'ın kullanıcının yapılandırma dosyalarını aradığı yeri tanımlar. Eğer ayarlanmamışsa $ HOME kullanılır, bu da neredeyse herkes için doğru değerdir.
Frank Terbeck

Yanıtlar:


94

Zsh'de, işlev arama yolu ($ fpath), içerdikleri işleve ilk kez ihtiyaç duyulduğunda otomatik olarak yüklenebilecek dosyaları içeren bir dizin dizisi tanımlar.

Zsh'ın iki otomatik yükleme dosyası modu vardır: Zsh'nin yerel yolu ve ksh'ın otomatik yükleme işlevine benzeyen başka bir mod. KSH_AUTOLOAD seçeneği ayarlanmışsa, ikincisi etkindir. Zsh'nin yerel modu varsayılandır ve burada başka bir yoldan tartışmayacağım (ksh tarzı otomatik yükleme hakkında ayrıntılar için bkz. "Man zshmisc" ve "man zshoptions").

Tamam. Bir dizinin `~ / .zfunc 'olduğunu ve işlev arama yolunun bir parçası olmasını istediğinizi varsayalım:

fpath=( ~/.zfunc "${fpath[@]}" )

Bu, özel dizini arama yolunun önüne ekler . Bu, zsh'nin kurulumundaki işlevleri kendikiyle geçersiz kılmak istiyorsanız (örneğin, zsh'ın CVS deposundaki '_git' gibi güncellenmiş bir tamamlama işlevini kullanarak kabuğun daha eski bir sürümüyle kullanmak istediğinizde) önemlidir.

Ayrıca, $ fpath dizinindeki dizinlerin özyinelemeli olarak aranmadığını da belirtmek gerekir. Özel dizininizin yinelenerek aranmasını istiyorsanız, bunun gibi kendinizle ilgilenmeniz gerekir (aşağıdaki kod parçası `EXTENDED_GLOB 'seçeneğinin ayarlanmasını gerektirir):

fpath=(
    ~/.zfuncs
    ~/.zfuncs/**/*~*/(CVS)#(/N)
    "${fpath[@]}"
)

Eğitimsiz bir göze şifreli görünebilir, ancak gerçekten sadece ~ / .zfunc altındaki tüm dizinleri $ fpath'a eklerken, "CVS" (yani bir bütününüzü kontrol etmeyi planlıyorsanız yararlıdır) işlev ağacını zsh’ın CVS’sinden özel arama yolunuza ekleyin).

Aşağıdaki satırı içeren bir dosya `~ / .zfunc / hello 'olduğunu varsayalım:

printf 'Hello world.\n'

Şimdi yapmanız gereken tek şey , ilk referansı üzerine otomatik olarak yüklenecek işlevi işaretlemektir :

autoload -Uz hello

“-Ez neyle ilgili?” Diye soruyorsunuz? Bu, sadece hangi seçeneklerin ayarlandığı fark etmeksizin, “autoload” ın doğru olanı yapmasına neden olacak bir seçenek kümesidir. “U”, işlev yüklenirken takma adın genişlemesini devre dışı bırakır ve “z”, “KSH_AUTOLOAD”, ne nedenle olursa olsun ayarlanmış olsa bile, zsh tarzı otomatik yüklemeyi zorlar.

Bundan sonra, yeni `merhaba 'işlevinizi kullanabilirsiniz:

% zsh merhaba
Selam Dünya.

Bu dosyaları bulmakla ilgili bir kelime: Bu sadece yanlış . Bu ~ / .zfunc / hello 'dosyasını besleyecekseniz, sadece "Merhaba dünya" yazacaktır. bir Zamanlar. Daha fazlası değil. Hiçbir fonksiyon tanımlanmayacak. Ve ayrıca, fikir olduğunda yalnızca işlevin kodunu yüklemektir gerekli . `Autoload sonra' işlevin tanımı çağrının değil okuyun. İşlev, gerektiğinde otomatik olarak yüklenecek şekilde işaretlendi.

Ve son olarak, $ FPATH ve $ fpath hakkında bir not: Zsh, bunları bağlantılı parametreler olarak tutar. Küçük harf parametresi bir dizidir. Büyük harfli sürüm, girişler arasında iki nokta üst üste gelen bağlantılı diziden gelen girişleri içeren bir skalar dizisidir. Bu yapılır, çünkü skalerlerin bir listesini kullanmak, skaler parametresini kullanan kodun geriye dönük uyumluluğunu korurken, aynı zamanda diziler kullanarak daha doğaldır. $ FPATH (skalar biri) kullanmayı seçerseniz, dikkatli olmanız gerekir:

FPATH=~/.zfunc:$FPATH

çalışacak, aşağıdaki çalışmaz:

FPATH="~/.zfunc:$FPATH"

Bunun nedeni, tilde genişlemesinin çift tırnak içinde gerçekleştirilmemesidir. Bu muhtemelen problemlerin kaynağı. Eğer echo $FPATHyaklaşık işareti değil genişletilmiş yol yazdırır o zaman çalışmaz. Güvende olmak için, böyle bir tilde yerine $ HOME kullanırdım:

FPATH="$HOME/.zfunc:$FPATH"

Olduğu söyleniyor, bu açıklamanın en üstünde yaptığım gibi array parametresini kullanmak istiyorum.

Ayrıca $ FPATH parametresini vermemelisiniz. Sadece mevcut kabuk işlemine ihtiyaç duyuyor, çocuklarından hiçbirine ihtiyaç duymuyor.

Güncelleme

'$ Fpath' içerisindeki dosyaların içeriği ile ilgili olarak:

Zsh tarzı otomatik yükleme ile, bir dosyanın içeriği tanımladığı işlevin gövdesidir. Böylece bir satır içeren "merhaba" adlı bir dosya echo "Hello world."tamamen "merhaba" adlı bir işlevi tanımlar. hello () { ... }Kodu koymakta özgürsün , ama bu gereksiz olurdu.

Bir dosyanın yalnızca bir işlev içerebileceği iddiası tam olarak doğru değildir.

Özellikle, fonksiyon bazlı tamamlama sisteminden (compsys) bazı fonksiyonlara bakarsanız, bunun yanlış bir kavram olduğunu hemen anlarsınız. Bir işlev dosyasında ek işlevler tanımlamakta özgürsünüz. Ayrıca, işlevin ilk çağrıldığı zaman yapmanız gerekebilecek herhangi bir başlatma işlemini de yapabilirsiniz. Ancak, her zaman dosyadaki dosya gibi adlandırılmış bir işlev tanımlayacak ve dosya sonunda bu işlevi çağıracaksınız , bu nedenle işleve ilk başvuruda bulunacak şekilde çalışacaktır.

Eğer - alt fonksiyonlarla - dosya içindeki dosya gibi bir fonksiyon tanımlamamışsanız, içinde fonksiyon tanımları olan (yani dosyadaki alt fonksiyonlarınkiler) bu fonksiyonla bitirdiniz. Dosya gibi adlandırılmış işlevi her çağırdığınızda, tüm alt işlevlerinizi etkin bir şekilde tanımlayacaksınız. Normalde, istediğiniz şey bu değildir , bu nedenle dosyadaki dosya gibi adlandırılan bir işlevi yeniden tanımlarsınız.

Size nasıl çalıştığı hakkında bir fikir verecek kısa bir iskelet ekleyeceğim:

# Let's again assume that these are the contents of a file called "hello".

# You may run arbitrary code in here, that will run the first time the
# function is referenced. Commonly, that is initialisation code. For example
# the `_tmux' completion function does exactly that.
echo initialising...

# You may also define additional functions in here. Note, that these
# functions are visible in global scope, so it is paramount to take
# care when you're naming these so you do not shadow existing commands or
# redefine existing functions.
hello_helper_one () {
    printf 'Hello'
}

hello_helper_two () {
    printf 'world.'
}

# Now you should redefine the "hello" function (which currently contains
# all the code from the file) to something that covers its actual
# functionality. After that, the two helper functions along with the core
# function will be defined and visible in global scope.
hello () {
    printf '%s %s\n' "$(hello_helper_one)" "$(hello_helper_two)"
}

# Finally run the redefined function with the same arguments as the current
# run. If this is left out, the functionality implemented by the newly
# defined "hello" function is not executed upon its first call. So:
hello "$@"

Bu aptal örneği çalıştırırsan, ilk çalıştırma şöyle görünür:

% zsh merhaba
initialising ...
Selam Dünya.

Ve ardışık çağrılar şöyle görünecek:

% zsh merhaba
Selam Dünya.

Umarım bu işleri temizler.

(Tüm bu püf noktaları kullanan daha karmaşık gerçek dünya örneklerinden biri, zsh'nin fonksiyon tabanlı tamamlama sisteminden daha önce bahsedilen ' _tmux ' işlevidir.)


Teşekkürler Frank! Diğer cevaplarda her dosya için sadece bir fonksiyon tanımlayabildiğimi okudum, doğru mu? Sana sözdizimini kullanın vermedi fark my_function () { }sizin de Hello worldörnek. Sözdizimi gerekli değilse, ne zaman kullanmanız faydalı olacaktır?
Amelio Vazquez-Reina

1
Bu soruları cevaplamak için orjinal cevabı genişlettim.
Frank Terbeck

“Her zaman dosyadaki dosya gibi adlandırılan işlevi tanımlayacak ve dosya sonunda bu işlevi çağıracaksınız”: neden?
Hibou57

Hibou57: (Bir kenara: Oradaki bir yazım hatası, "bir fonksiyon tanımla" olmalı, şimdi düzeltildi.) Göz önünde bulundurulan kod parçacığını aldığınızda bunun net olduğunu düşündüm. Her neyse, nedeni biraz daha açıklayan bir paragraf ekledim.
Frank Terbeck

İşte sitenizden daha fazla bilgi, teşekkürler.
Timo

5

Bir fpathelemanın isimlendirdiği bir dizindeki dosyanın ismi, tanımlayabileceği otomatik yüklenebilir fonksiyonun ismi ile aynı olmalıdır.

İşleviniz isimlendirilir my_functionve ~/.my_zsh_functionssizin için amaçlanan dizindir fpath, yani tanımı my_functiondosyada olmalıdır ~/.my_zsh_functions/my_function.

Önerilen dosya adındaki ( functions_1) çoğul , dosyaya birden çok işlev eklemeyi planladığınızı gösterir. Bu nasıl fpathve otomatik yükleme işe yaramaz. Dosya başına bir fonksiyon tanımına sahip olmalısınız.


2

source ~/.my_zsh_functions/functions1Terminalde verin ve değerlendirin my_function, şimdi fonksiyonu çağırabileceksiniz.


2
Teşekkürler ama rolü nedir FPATHve autoloado zaman? Neden dosyayı da kaynaklandırmam gerekiyor? Güncellenmiş soruma bakın.
Amelio Vazquez-Reina

1

Tüm işlevlerinizi içeren bir dosyayı $ ZDOTDIR / .zshrc dosyasında şöyle yükleyebilirsiniz:

source $ZDOTDIR/functions_file

Veya "." Bir nokta kullanabilir. "kaynak" yerine.


1
Teşekkürler ama rolü nedir FPATHve autoloado zaman? Neden dosyayı da kaynaklandırmam gerekiyor? Güncellenmiş soruma bakın.
Amelio Vazquez-Reina

0

Kaynak bulma kesinlikle doğru bir yaklaşım değildir, çünkü istediğiniz görünüşte tembel olarak başlatılmış işlevler kullanmaktır. Bunun autoloadiçin var. İşte peşinde olduğun şeyi nasıl başardığın.

İçinde, "ekos" merhaba dünyası " ~/.my_zsh_functionsdenilen bir işlev koymak istediğini söylüyorsun my_function. Ancak, bir işlev çağrısına sarılırsınız, bu şekilde çalışmaz. Bunun yerine, adlı bir dosya oluşturmanız gerekir ~/.my_zsh_functions/my_function. İçine, sadece echo "Hello world"bir fonksiyon paketleyicisine değil, koyun . Sarıcıyı gerçekten tercih etmeyi tercih ediyorsanız, böyle bir şey de yapabilirsiniz.

# ~/.my_zsh_functions/my_function
__my_function () {
    echo "Hello world";
}
# you have to call __my_function
# if this is how you choose to do it
__my_function

Sonra, .zshrcdosyanıza aşağıdakileri ekleyin:

fpath=(~/.my_zsh_functions $fpath);
autoload -U ~/.my_zsh_functions/my_function

Yeni bir ZSH kabuğu yüklediğinizde, yazın which my_function. Bunu görmelisin:

my_function () {
    # undefined
    builtin autoload -XU
}

ZSH, senin için benim fonksiyonumdan vazgeçti autoload -X. Şimdi koş my_functionama sadece yazarak my_function. Çıktısını görmelisiniz Hello worldve şimdi çalıştırdığınızda which my_functionbu şekilde doldurulmuş işlevi görmelisiniz:

my_function () {
    echo "Hello world"
}

Şimdi, asıl sihir ~/.my_zsh_functionsçalışmak için tüm klasörünüzü ayarladığınızda gelir autoload. Bu klasöre bıraktığınız her dosyanın bu şekilde çalışmasını istiyorsanız, içine .zshrceklediklerinizi şu şekilde değiştirin:

# add ~/.my_zsh_functions to fpath, and then lazy autoload
# every file in there as a function
fpath=(~/.my_zsh_functions $fpath);
autoload -U fpath[1]/*(.:t)
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.