Cara mendefinisikan dan memuat fungsi shell Anda sendiri di zsh

55

Saya kesulitan mendefinisikan dan menjalankan fungsi shell saya sendiri di zsh. Saya mengikuti instruksi pada dokumentasi resmi dan mencoba dengan contoh mudah terlebih dahulu, tetapi saya gagal membuatnya bekerja.

Saya punya folder:

~/.my_zsh_functions

Dalam folder ini saya memiliki file yang dipanggil functions_1dengan rwxizin pengguna. Dalam file ini saya mendefinisikan fungsi shell berikut:

my_function () {
echo "Hello world";
}

Saya menetapkan FPATHuntuk memasukkan path ke folder ~/.my_zsh_functions:

export FPATH=~/.my_zsh_functions:$FPATH

Saya dapat mengkonfirmasi bahwa folder .my_zsh_functionstersebut ada di jalur fungsi dengan echo $FPATHatauecho $fpath

Namun, jika saya mencoba hal berikut dari shell:

> autoload my_function
> my_function

Saya mendapat:

zsh: my_test_function: file definisi fungsi tidak ditemukan

Apakah ada hal lain yang perlu saya lakukan untuk dapat menelepon my_function?

Memperbarui:

Jawaban sejauh ini menyarankan sumber file dengan fungsi zsh. Ini masuk akal, tetapi saya agak bingung. Bukankah seharusnya zsh tahu di mana file-file itu berada FPATH? Apa tujuan dari autoloaditu?

Amelio Vazquez-Reina
sumber
Pastikan Anda telah menetapkan $ ZDOTDIR dengan benar. zsh.sourceforge.net/Intro/intro_3.html
ramonovski
2
Nilai $ ZDOTDIR tidak terkait dengan masalah ini. Variabel menentukan tempat zsh mencari file konfigurasi pengguna. Jika tidak disetel $ HOME digunakan sebagai gantinya, yang merupakan nilai yang tepat untuk hampir semua orang.
Frank Terbeck

Jawaban:

96

Di zsh, path pencarian fungsi ($ fpath) mendefinisikan satu set direktori, yang berisi file yang dapat ditandai untuk dimuat secara otomatis ketika fungsi yang dikandungnya diperlukan untuk pertama kalinya.

Zsh memiliki dua mode file autoloading: Cara asli Zsh dan mode lain yang menyerupai autoloading ksh. Yang terakhir aktif jika opsi KSH_AUTOLOAD diatur. Mode asli Zsh adalah default dan saya tidak akan membahas cara lain di sini (lihat "man zshmisc" dan "man zshoptions" untuk detail tentang autoloading gaya ksh).

Baik. Katakanlah Anda mendapat direktori `~ / .zfunc 'dan Anda ingin itu menjadi bagian dari jalur pencarian fungsi, Anda melakukan ini:

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

Itu menambahkan direktori pribadi Anda ke bagian depan jalur pencarian. Itu penting jika Anda ingin menimpa fungsi dari instalasi zsh dengan milik Anda sendiri (seperti, ketika Anda ingin menggunakan fungsi penyelesaian yang diperbarui seperti `_git 'dari repositori zsh's CVS dengan versi shell yang diinstal lebih lama).

Perlu juga dicatat, bahwa direktori dari `$ fpath 'tidak dicari secara rekursif. Jika Anda ingin direktori pribadi Anda dicari secara rekursif, Anda harus mengurusnya sendiri, seperti ini (cuplikan berikut membutuhkan opsi `EXTENDED_GLOB 'untuk diatur):

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

Ini mungkin terlihat samar untuk mata yang tidak terlatih, tetapi itu benar-benar hanya menambahkan semua direktori di bawah `~ / .zfunc 'ke` $ fpath', sementara mengabaikan direktori yang disebut "CVS" (yang berguna, jika Anda berencana untuk checkout keseluruhan pohon fungsi dari CVS zsh ke jalur pencarian pribadi Anda).

Mari kita asumsikan Anda punya file `~ / .zfunc / hello 'yang berisi baris berikut:

printf 'Hello world.\n'

Yang perlu Anda lakukan sekarang adalah menandai fungsi yang akan dimuat secara otomatis pada referensi pertamanya:

autoload -Uz hello

"Apa -Uz tentang?", Anda bertanya? Yah, itu hanya satu set opsi yang akan menyebabkan `autoload 'untuk melakukan hal yang benar, tidak peduli opsi apa yang sedang diatur sebaliknya. `U 'menonaktifkan ekspansi alias saat fungsi sedang dimuat dan` z' memaksa gaya-zsh autoloading bahkan jika `KSH_AUTOLOAD 'diatur untuk alasan apa pun.

Setelah itu diatasi, Anda dapat menggunakan fungsi `halo 'baru Anda:

zsh% halo
Halo Dunia.

Sepatah kata tentang sumber file-file ini: Itu hanya salah . Jika Anda akan sumber file `~ / .zfunc / hello ', itu hanya akan mencetak" Hello world. " sekali. Tidak ada lagi. Tidak ada fungsi yang akan ditentukan. Dan selain itu, idenya adalah untuk hanya memuat kode fungsi saat diperlukan . Setelah panggilan `pengisian otomatis ', definisi fungsi tidak dibaca. Fungsi baru saja ditandai untuk diisikan otomatis nanti sesuai kebutuhan.

Dan akhirnya, catatan tentang $ FPATH dan $ fpath: Zsh mempertahankan itu sebagai parameter yang ditautkan. Parameter huruf kecil adalah sebuah array. Versi huruf besar adalah skalar string, yang berisi entri dari array tertaut yang bergabung dengan titik dua di antara entri. Ini dilakukan, karena menangani daftar skalar adalah cara yang lebih alami menggunakan array, sementara juga menjaga kompatibilitas mundur untuk kode yang menggunakan parameter skalar. Jika Anda memilih untuk menggunakan $ FPATH (skalar), Anda harus berhati-hati:

FPATH=~/.zfunc:$FPATH

akan berfungsi, sementara yang berikut tidak akan:

FPATH="~/.zfunc:$FPATH"

Alasannya adalah bahwa ekspansi tilde tidak dilakukan dalam tanda kutip ganda. Ini kemungkinan sumber masalah Anda. Jika echo $FPATHmencetak tilde dan bukan jalur yang diperluas maka itu tidak akan berfungsi. Agar aman, saya akan menggunakan $ HOME alih-alih tilde seperti ini:

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

Yang sedang berkata, saya lebih suka menggunakan parameter array seperti yang saya lakukan di bagian atas penjelasan ini.

Anda juga tidak boleh mengekspor parameter $ FPATH. Ini hanya dibutuhkan oleh proses shell saat ini dan bukan oleh anak-anaknya.

Memperbarui

Mengenai isi file dalam `$ fpath ':

Dengan gaya-otomatis zsh, konten file adalah isi dari fungsi yang ditentukannya. Dengan demikian file bernama "halo" yang mengandung garis echo "Hello world."sepenuhnya mendefinisikan fungsi yang disebut "halo". Anda bebas untuk memasukkan hello () { ... }kode, tetapi itu akan berlebihan.

Klaim bahwa satu file hanya boleh berisi satu fungsi tidak sepenuhnya benar.

Terutama jika Anda melihat beberapa fungsi dari fungsi sistem penyelesaian berbasis (compsys) Anda akan segera menyadari bahwa itu adalah kesalahpahaman. Anda bebas menentukan fungsi tambahan dalam file fungsi. Anda juga bebas melakukan inisialisasi apa pun, yang mungkin perlu Anda lakukan saat fungsi pertama kali dipanggil. Namun, ketika Anda melakukannya, Anda akan selalu mendefinisikan fungsi yang dinamai seperti file dalam file dan memanggil fungsi itu di akhir file, sehingga dijalankan saat pertama kali fungsi direferensikan.

Jika - dengan sub-fungsi - Anda tidak mendefinisikan fungsi yang dinamai seperti file di dalam file, Anda akan berakhir dengan fungsi yang memiliki definisi fungsi di dalamnya (yaitu sub-fungsi dalam file). Anda secara efektif akan mendefinisikan semua sub-fungsi Anda setiap kali Anda memanggil fungsi yang dinamai seperti file. Biasanya, itu bukan yang Anda inginkan, jadi Anda akan mendefinisikan ulang suatu fungsi, yang dinamai seperti file di dalam file.

Saya akan menyertakan kerangka pendek, yang akan memberi Anda gambaran tentang cara kerjanya:

# 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 "$@"

Jika Anda menjalankan contoh konyol ini, proses pertama akan terlihat seperti ini:

zsh% halo
menginisialisasi ...
Halo Dunia.

Dan panggilan berurutan akan terlihat seperti ini:

zsh% halo
Halo Dunia.

Saya harap ini jelas.

(Salah satu contoh dunia nyata yang lebih kompleks yang menggunakan semua trik itu adalah fungsi ` _tmux 'yang telah disebutkan dari sistem penyelesaian berbasis fungsi zsh.)

Frank Terbeck
sumber
Frank terima kasih! Saya membaca jawaban lain bahwa saya hanya dapat mendefinisikan satu fungsi per file, apakah itu benar? Saya perhatikan Anda tidak menggunakan sintaks my_function () { }dalam Hello worldcontoh Anda . Jika sintaksis tidak diperlukan, kapan akan berguna untuk menggunakannya?
Amelio Vazquez-Reina
1
Saya memberikan jawaban asli untuk menjawab pertanyaan-pertanyaan itu juga.
Frank Terbeck
“Anda akan selalu mendefinisikan dalam fungsi yang dinamai seperti file dalam file dan memanggil fungsi itu di akhir file”: mengapa demikian?
Hibou57
Hibou57: (Selain itu: kesalahan ketik di sana, harus "mendefinisikan fungsi", itu sudah diperbaiki sekarang.) Saya pikir itu sudah jelas, ketika Anda mengambil cuplikan kode yang mengikuti pertimbangan. Ngomong-ngomong, saya sudah menambahkan paragraf yang menguraikan alasannya sedikit lebih harfiah.
Frank Terbeck
Berikut ini info lebih lanjut dari situs Anda, terima kasih.
Timo
5

Nama file dalam direktori yang dinamai oleh suatu fpathelemen harus cocok dengan nama fungsi yang dapat di-autoload yang didefinisikannya.

Fungsi Anda dinamai my_functiondan ~/.my_zsh_functionsmerupakan direktori yang dimaksud di Anda fpath, sehingga definisi my_functionharus ada dalam file ~/.my_zsh_functions/my_function.

Bentuk jamak dalam nama file yang Anda usulkan ( functions_1) menunjukkan bahwa Anda berencana untuk meletakkan beberapa fungsi dalam file. Ini bukan bagaimana fpathdan autoloading bekerja. Anda harus memiliki satu definisi fungsi per file.

Chris Johnsen
sumber
2

berikan source ~/.my_zsh_functions/functions1di terminal dan evaluasi my_function, sekarang Anda akan dapat memanggil fungsi

harish.venkat
sumber
2
Terima kasih, tapi apa perannya FPATHdan autoloadkemudian? Mengapa saya juga perlu sumber file? Lihat pertanyaan saya yang diperbarui.
Amelio Vazquez-Reina
1

Anda dapat "memuat" file dengan semua fungsi Anda di $ ZDOTDIR / .zshrc Anda seperti ini:

source $ZDOTDIR/functions_file

Atau bisa menggunakan titik "." bukannya "sumber".

ramonovski
sumber
1
Terima kasih, tapi apa perannya FPATHdan autoloadkemudian? Mengapa saya juga perlu sumber file? Lihat pertanyaan saya yang diperbarui.
Amelio Vazquez-Reina
0

Sourcing jelas bukan pendekatan yang tepat, karena yang tampaknya Anda inginkan adalah memiliki fungsi inisialisasi malas. Itu untuk apa autoload. Inilah cara Anda mencapai apa yang Anda cari.

Di Anda ~/.my_zsh_functions, Anda mengatakan Anda ingin meletakkan fungsi yang disebut my_functiongema "halo dunia". Tapi, Anda membungkusnya dalam panggilan fungsi, yang bukan cara kerjanya. Sebagai gantinya, Anda perlu membuat file bernama ~/.my_zsh_functions/my_function. Di dalamnya, cukup masukkan echo "Hello world", bukan dalam fungsi wrapper. Anda juga dapat melakukan sesuatu seperti ini jika Anda benar-benar memilih untuk memiliki bungkusnya.

# ~/.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

Selanjutnya, di .zshrcfile Anda , tambahkan berikut ini:

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

Saat Anda memuat shell ZSH baru, ketik which my_function. Anda harus melihat ini:

my_function () {
    # undefined
    builtin autoload -XU
}

ZSH baru saja mematikan fungsi my_fungsi untuk Anda autoload -X. Sekarang, jalankan my_functiontetapi ketikkan saja my_function. Anda akan melihat hasil Hello worldcetak, dan sekarang saat Anda menjalankannya which my_functionAnda akan melihat fungsi yang diisi seperti ini:

my_function () {
    echo "Hello world"
}

Sekarang, keajaiban yang sebenarnya datang ketika Anda mengatur seluruh ~/.my_zsh_functionsfolder Anda untuk bekerja dengannya autoload. Jika Anda ingin setiap file yang Anda jatuhkan di folder ini berfungsi seperti ini, ubah apa yang Anda masukkan .zshrcke dalam sesuatu ini:

# 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)
mattmc3
sumber