วิธีกำหนดและโหลดฟังก์ชันเชลล์ของคุณเองใน zsh


54

ฉันมีช่วงเวลาที่ยากลำบากในการกำหนดและเรียกใช้ฟังก์ชันเชลล์ของฉันเองใน zsh ฉันทำตามคำแนะนำในเอกสารอย่างเป็นทางการและลองใช้ตัวอย่างง่ายๆก่อน แต่ฉันไม่สามารถทำงานได้

ฉันมีโฟลเดอร์:

~/.my_zsh_functions

ในโฟลเดอร์นี้ฉันมีไฟล์ชื่อที่functions_1มีrwxสิทธิ์ผู้ใช้ ในไฟล์นี้ฉันได้กำหนดฟังก์ชั่นเชลล์ต่อไปนี้:

my_function () {
echo "Hello world";
}

ฉันกำหนดFPATHให้ใส่เส้นทางไปยังโฟลเดอร์~/.my_zsh_functions:

export FPATH=~/.my_zsh_functions:$FPATH

ฉันสามารถยืนยันได้ว่าโฟลเดอร์.my_zsh_functionsอยู่ในเส้นทางของฟังก์ชั่นด้วยecho $FPATHหรือecho $fpath

อย่างไรก็ตามถ้าฉันลองทำสิ่งต่อไปนี้จากเชลล์:

> autoload my_function
> my_function

ฉันเข้าใจ:

zsh: my_test_function: ไม่พบไฟล์การกำหนดฟังก์ชัน

มีอะไรอีกบ้างที่ฉันต้องทำเพื่อให้สามารถโทรได้my_function?

ปรับปรุง:

คำตอบเพื่อให้ห่างไกลแนะนำการจัดหาไฟล์ด้วยฟังก์ชั่น zsh มันสมเหตุสมผลแล้ว แต่ฉันก็สับสนเล็กน้อย zsh ไม่ควรรู้ว่าไฟล์เหล่านั้นอยู่FPATHที่ไหน? ตอนนั้นมีจุดประสงค์autoloadอะไร?


ตรวจสอบให้แน่ใจว่าคุณได้กำหนด $ ZDOTDIR ไว้อย่างเหมาะสม zsh.sourceforge.net/Intro/intro_3.html
ramonovski

2
ค่าของ $ ZDOTDIR ไม่เกี่ยวข้องกับปัญหานี้ ตัวแปรกำหนดว่า zsh กำลังมองหาไฟล์กำหนดค่าของผู้ใช้ หากไม่ได้กำหนด $ HOME จะใช้แทนซึ่งเป็นค่าที่เหมาะสมสำหรับเกือบทุกคน
Frank Terbeck

คำตอบ:


94

ใน zsh เส้นทางการค้นหาฟังก์ชั่น ($ fpath) กำหนดชุดของไดเรกทอรีซึ่งมีไฟล์ที่สามารถทำเครื่องหมายให้โหลดโดยอัตโนมัติเมื่อจำเป็นต้องใช้ฟังก์ชั่นในครั้งแรก

Zsh มีไฟล์การโหลดอัตโนมัติสองโหมด: วิธีดั้งเดิมของ Zsh และโหมดอื่นที่คล้ายกับการโหลดอัตโนมัติของ ksh ส่วนหลังใช้งานได้หากตั้งค่าตัวเลือก KSH_AUTOLOAD โหมดเนทิฟของ Zsh เป็นค่าเริ่มต้นและฉันจะไม่พูดถึงวิธีอื่นที่นี่ (ดู "man zshmisc" และ "man zshoptions" สำหรับรายละเอียดเกี่ยวกับการโหลดอัตโนมัติสไตล์ ksh)

ถูก สมมติว่าคุณมีไดเรกทอรี `~ / .zfunc 'และคุณต้องการให้มันเป็นส่วนหนึ่งของเส้นทางการค้นหาฟังก์ชั่นคุณทำสิ่งนี้:

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

ที่เพิ่มไดเรกทอรีส่วนตัวของคุณที่ด้านหน้าของเส้นทางการค้นหา นั่นเป็นสิ่งสำคัญหากคุณต้องการแทนที่ฟังก์ชั่นจากการติดตั้งของ zsh ด้วยตัวคุณเอง (เช่นเมื่อคุณต้องการใช้ฟังก์ชั่นเสร็จสิ้นการอัปเดตเช่น `_git 'จากที่เก็บ CVS ของ zsh ด้วยเชลล์รุ่นเก่าที่ติดตั้ง)

นอกจากนี้ยังเป็นที่น่าสังเกตว่าไดเรกทอรีจาก `$ fpath 'จะไม่ถูกเรียกซ้ำ หากคุณต้องการให้ไดเรกทอรีส่วนตัวของคุณถูกค้นหาซ้ำคุณจะต้องดูแลตัวเองเช่นนี้ (ข้อมูลโค้ดต่อไปนี้ต้องการตัวเลือก `EXTENDED_GLOB '):

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

มันอาจดูเป็นความลับในสายตาที่ไม่ได้ฝึกฝน แต่จริงๆแล้วมันแค่เพิ่มไดเรกทอรีทั้งหมดที่อยู่ด้านล่าง `~ / .zfunc 'เป็น` $ fpath' ในขณะที่ไม่สนใจไดเรกทอรีที่เรียกว่า "CVS" (ซึ่งมีประโยชน์หากคุณวางแผนจะชำระเงินทั้งหมด ต้นไม้ฟังก์ชั่นจาก CVS ของ zsh ในเส้นทางการค้นหาส่วนตัวของคุณ)

สมมติว่าคุณมีไฟล์ `~ / .zfunc / hello 'ที่มีบรรทัดต่อไปนี้:

printf 'Hello world.\n'

สิ่งที่คุณต้องทำตอนนี้คือทำเครื่องหมายฟังก์ชั่นที่จะโหลดโดยอัตโนมัติตามการอ้างอิงครั้งแรก:

autoload -Uz hello

"-Uz เกี่ยวกับอะไร" คุณถาม? นั่นเป็นเพียงชุดของตัวเลือกที่จะทำให้ 'autoload' ทำสิ่งที่ถูกต้องไม่ว่าตัวเลือกใดจะถูกตั้งค่าเป็นอย่างอื่น `U 'ปิดใช้งานการขยายนามแฝงขณะที่ฟังก์ชันกำลังโหลดและ` z' บังคับให้โหลดอัตโนมัติสไตล์ zsh แม้ว่า `KSH_AUTOLOAD 'จะถูกตั้งค่าด้วยเหตุผลใดก็ตาม

หลังจากนั้นได้รับการดูแลคุณสามารถใช้ฟังก์ชั่น `สวัสดี 'ใหม่ของคุณ:

สวัสดี
สวัสดีชาวโลก.

คำเกี่ยวกับการจัดหาไฟล์เหล่านี้: นั่นเป็นเพียงที่ไม่ถูกต้อง หากคุณต้องการหาไฟล์ `~ / .zfunc / hello 'มันจะพิมพ์" Hello world " ครั้งหนึ่ง ไม่มีอะไรเพิ่มเติม จะไม่มีการกำหนดฟังก์ชั่น นอกจากนี้แนวคิดก็คือให้โหลดโค้ดของฟังก์ชันเมื่อจำเป็นเท่านั้น หลังจากการเรียก 'autoload' ความหมายของฟังก์ชันจะไม่อ่าน ฟังก์ชันถูกทำเครื่องหมายว่าจะโหลดอัตโนมัติในภายหลังตามต้องการ

และสุดท้ายโน้ตเกี่ยวกับ $ FPATH และ $ fpath: Zsh จะรักษาพารามิเตอร์เหล่านั้นไว้เป็นพารามิเตอร์ที่เชื่อมโยง พารามิเตอร์ตัวพิมพ์เล็กคืออาร์เรย์ เวอร์ชันตัวพิมพ์ใหญ่เป็นสเกลาร์สตริงที่มีรายการจากอาร์เรย์ที่เชื่อมโยงซึ่งเชื่อมโยงกันโดยโคลอนในระหว่างรายการ สิ่งนี้เสร็จสิ้นเนื่องจากการจัดการรายการสเกลาร์เป็นวิธีที่เป็นธรรมชาติมากขึ้นโดยใช้อาร์เรย์ในขณะที่ยังคงรักษาความเข้ากันได้แบบย้อนหลังสำหรับโค้ดที่ใช้พารามิเตอร์สเกลาร์ หากคุณเลือกใช้ $ FPATH (scalar one) คุณต้องระวัง:

FPATH=~/.zfunc:$FPATH

จะทำงานในขณะที่สิ่งต่อไปนี้จะไม่:

FPATH="~/.zfunc:$FPATH"

เหตุผลคือการขยายตัวหนอนไม่ได้ดำเนินการภายในเครื่องหมายคำพูดคู่ นี่เป็นสาเหตุของปัญหาของคุณ หากecho $FPATHพิมพ์เครื่องหมายตัวหนอนและไม่ใช่เส้นทางแบบขยายก็จะไม่ทำงาน เพื่อความปลอดภัยฉันจะใช้ $ HOME แทนตัวหนอนแบบนี้:

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

ที่ถูกกล่าวว่าฉันค่อนข้างจะใช้พารามิเตอร์อาร์เรย์เหมือนที่ฉันได้ที่ด้านบนของคำอธิบายนี้

คุณไม่ควรส่งออกพารามิเตอร์ $ FPATH มันจำเป็นเท่านั้นโดยกระบวนการเชลล์ปัจจุบันและไม่ใช่โดยลูกใด ๆ ของมัน

ปรับปรุง

เกี่ยวกับเนื้อหาของไฟล์ใน `$ fpath ':

ด้วยการโหลดอัตโนมัติสไตล์ zsh เนื้อหาของไฟล์คือส่วนของฟังก์ชั่นที่กำหนด ดังนั้นไฟล์ที่ชื่อ "hello" ที่มีบรรทัดecho "Hello world."จะกำหนดฟังก์ชันที่เรียกว่า "hello" อย่างสมบูรณ์ คุณมีอิสระที่จะใส่ hello () { ... }รหัส แต่มันจะไม่จำเป็น

การอ้างสิทธิ์ว่าไฟล์หนึ่งไฟล์อาจมีเพียงหนึ่งฟังก์ชันไม่ถูกต้องทั้งหมด

โดยเฉพาะอย่างยิ่งถ้าคุณดูฟังก์ชั่นบางอย่างจากฟังก์ชั่นการเติมเต็มระบบ (compsys) คุณจะรู้ได้อย่างรวดเร็วว่านั่นเป็นความเข้าใจผิด คุณมีอิสระในการกำหนดฟังก์ชั่นเพิ่มเติมในไฟล์ฟังก์ชั่น คุณมีอิสระที่จะทำการเริ่มต้นประเภทใดก็ได้ซึ่งคุณอาจจำเป็นต้องทำในครั้งแรกที่มีการเรียกใช้ฟังก์ชัน อย่างไรก็ตามเมื่อคุณทำคุณจะกำหนดฟังก์ชั่นที่มีชื่อเหมือนไฟล์ในไฟล์และเรียกใช้ฟังก์ชั่นนั้นในตอนท้ายของไฟล์ดังนั้นมันจะถูกเรียกใช้ในครั้งแรกที่มีการอ้างอิงฟังก์ชัน

ถ้า - ด้วยฟังก์ชั่นย่อย - คุณไม่ได้กำหนดฟังก์ชั่นที่ชื่อเหมือนไฟล์ภายในไฟล์คุณจะท้ายด้วยฟังก์ชั่นที่มีคำนิยามฟังก์ชั่นอยู่ในนั้น (กล่าวคือฟังก์ชั่นย่อยในไฟล์) คุณจะต้องกำหนดฟังก์ชั่นย่อยทั้งหมดอย่างมีประสิทธิภาพทุกครั้งที่คุณเรียกใช้ฟังก์ชันที่มีชื่อเหมือนไฟล์ โดยปกตินั่นไม่ใช่สิ่งที่คุณต้องการดังนั้นคุณจึงต้องกำหนดฟังก์ชันใหม่อีกครั้งซึ่งมีชื่อเหมือนกับไฟล์ภายในไฟล์

ฉันจะรวมโครงกระดูกสั้น ๆ ที่จะทำให้คุณมีความคิดในการทำงาน:

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

หากคุณเรียกใช้ตัวอย่างที่งี่เง่าการเรียกใช้ครั้งแรกจะมีลักษณะดังนี้:

สวัสดี
initialising ...
สวัสดีชาวโลก.

และการโทรติดต่อกันจะมีลักษณะเช่นนี้:

สวัสดี
สวัสดีชาวโลก.

ฉันหวังว่านี่จะช่วยล้างสิ่งต่างๆ

(หนึ่งในตัวอย่างของโลกแห่งความซับซ้อนที่ใช้กลอุบายทั้งหมดเหล่านี้คือฟังก์ชั่น` _tmux 'ที่กล่าวถึงแล้วจากระบบเสร็จสมบูรณ์ของฟังก์ชันของ zsh)


ขอบคุณแฟรงค์! ฉันอ่านคำตอบอื่น ๆ ที่ฉันสามารถกำหนดได้เพียงหนึ่งฟังก์ชันต่อไฟล์ใช่มั้ย ฉันสังเกตว่าคุณไม่ได้ใช้ไวยากรณ์my_function () { }ในHello worldตัวอย่างของคุณ ถ้าไม่จำเป็นต้องใช้ซินแท็กซ์มันจะมีประโยชน์ในการใช้มันเมื่อไหร่?
Amelio Vazquez-Reina

1
ฉันขยายคำตอบดั้งเดิมเพื่อตอบคำถามเหล่านั้นเช่นกัน
Frank Terbeck

“ คุณจะกำหนดฟังก์ชันที่ชื่อเหมือนไฟล์ในไฟล์และเรียกใช้ฟังก์ชันนั้นเมื่อสิ้นสุดไฟล์”: ทำไมเหรอ?
Hibou57

Hibou57: (นอกเหนือจาก: นั่นคือการพิมพ์ผิดที่ควรจะเป็น "กำหนดฟังก์ชั่น" ที่ได้รับการแก้ไขในขณะนี้) ฉันคิดว่าชัดเจนเมื่อคุณนำข้อมูลโค้ดที่จะพิจารณา อย่างไรก็ตามฉันได้เพิ่มย่อหน้าที่สะกดเหตุผลออกไปอีกหน่อย
Frank Terbeck

นี่คือข้อมูลเพิ่มเติมจากเว็บไซต์ของคุณขอบคุณ
Timo

5

ชื่อของไฟล์ในไดเรกทอรีที่ตั้งชื่อโดยfpathองค์ประกอบจะต้องตรงกับชื่อของฟังก์ชั่น autoloadable ที่กำหนด

ฟังก์ชั่นของคุณเป็นชื่อmy_functionและ~/.my_zsh_functionsเป็นไดเรกทอรีตั้งใจในของคุณfpathเพื่อให้ความหมายของควรจะอยู่ในไฟล์my_function~/.my_zsh_functions/my_function

พหูพจน์ในชื่อไฟล์ที่คุณเสนอ ( functions_1) แสดงว่าคุณวางแผนที่จะวางฟังก์ชั่นหลาย ๆ ไฟล์ไว้ นี่ไม่ใช่วิธีการfpathและการโหลดอัตโนมัติ คุณควรมีหนึ่งนิยามฟังก์ชันต่อหนึ่งไฟล์


2

ให้source ~/.my_zsh_functions/functions1ในเทอร์มินัลและการประเมินผลmy_functionตอนนี้คุณจะสามารถเรียกใช้ฟังก์ชันได้


2
ขอบคุณ แต่สิ่งที่เป็นบทบาทของFPATHและautoloadแล้ว? เหตุใดฉันจึงต้องจัดหาไฟล์ด้วย ดูคำถามที่อัปเดตของฉัน
Amelio Vazquez-Reina

1

คุณสามารถ "โหลด" ไฟล์ที่มีฟังก์ชั่นทั้งหมดของคุณใน $ ZDOTDIR / .zshrc ของคุณเช่นนี้:

source $ZDOTDIR/functions_file

หรือสามารถใช้จุด "." แทน "แหล่งที่มา"


1
ขอบคุณ แต่สิ่งที่เป็นบทบาทของFPATHและautoloadแล้ว? เหตุใดฉันจึงต้องจัดหาไฟล์ด้วย ดูคำถามที่อัปเดตของฉัน
Amelio Vazquez-Reina

0

การจัดหาไม่ใช่วิธีที่ถูกต้องอย่างแน่นอนเนื่องจากสิ่งที่คุณต้องการคือการมีฟังก์ชั่นเริ่มต้นที่ขี้เกียจ นั่นคือสิ่งที่autoloadมีไว้เพื่อ นี่คือวิธีที่คุณบรรลุสิ่งที่คุณทำ

ในตัวคุณ~/.my_zsh_functionsคุณบอกว่าคุณต้องการใส่ฟังก์ชั่นที่เรียกmy_functionว่า echos "hello world" แต่คุณพันไว้ในการเรียกใช้ฟังก์ชันซึ่งไม่ใช่วิธีการทำงาน คุณต้องสร้างไฟล์~/.my_zsh_functions/my_functionแทน ในนั้นเพียงแค่ใส่echo "Hello world"ไม่ได้อยู่ในห่อหุ้มฟังก์ชั่น คุณสามารถทำอะไรแบบนี้ได้ถ้าคุณอยากมีเสื้อคลุม

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

ถัดไปใน.zshrcไฟล์ของคุณเพิ่มต่อไปนี้:

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

เมื่อคุณโหลดเปลือก ZSH which my_functionใหม่ประเภท คุณควรเห็นสิ่งนี้:

my_function () {
    # undefined
    builtin autoload -XU
}

ZSH เพียง stubbed ออก my_function autoload -Xสำหรับคุณด้วย ตอนนี้ทำงานแต่เพียงแค่พิมพ์my_function my_functionคุณควรเห็นHello worldพิมพ์ออกมาและตอนนี้เมื่อคุณเรียกใช้which my_functionคุณจะเห็นฟังก์ชั่นที่กรอกข้อมูลในลักษณะนี้

my_function () {
    echo "Hello world"
}

ตอนนี้ความมหัศจรรย์ที่แท้จริงมาเมื่อคุณตั้งค่าของคุณทั้งโฟลเดอร์ที่จะทำงานกับ~/.my_zsh_functions autoloadหากคุณต้องการให้ทุกไฟล์ที่คุณวางไว้ในโฟลเดอร์นี้ทำงานเช่นนี้ให้เปลี่ยนสิ่งที่คุณใส่ไว้ในไฟล์.zshrcนี้:

# 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)
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.