zsh 有類似閉包的東西嗎?

zsh 有類似閉包的東西嗎?

我剛剛決定嘗試 zsh (透過 oh-my-zsh),現在正在嘗試precmd模擬一個兩行提示,該提示不僅在最後一行中有正確的提示。

所以我克隆了預設主題,並受到啟發這個帖子(我也用它來學習很多東西),我做了這樣的事情(稍後我會添加顏色):

function precmd {
    local cwd="${(%):-[%~]}"
    local who_where="${(%):-%n@%m}"
    local git_info=${(%)$(git_prompt_info)}
    local right_prompt="     $git_info [$who_where]"
    local left_prompt="${(r:(($COLUMNS - ${#${right_prompt}})):: :)cwd}"

    echo "$left_prompt$right_prompt"
}

它有效。但我忍不住想知道:zsh 是否在每次呼叫 precmd 時定義所有這些變數?

我一直在谷歌搜尋與 zsh 相關的閉包、作用域和命名空間,希望將本地變量作為數據附加到 precmd,因此不需要每次都重新定義變量,但我什麼也沒找到。有什麼方法可以做我正在嘗試的事情,還是我應該放棄它?

作為旁注,並且僅當相關時,“加載函數”是什麼意思?

答案1

Zsh 沒有諸如閉包、包或命名空間之類的東西。 Zsh 缺少一些實現真正的閉包所需的東西:

  • 功能不是一流的。您不能將函數作為參數傳遞給其他函數,且函數不能傳回其他函數。 (您可以透過姓名要呼叫的函數的名稱,但這與傳遞函數本身不同)。

  • 不能有巢狀函數。 zsh 中的所有函數都是全域的。您必須為函數名稱加上前綴以避免衝突。特別注意,函數將隱藏同名的外部程式。如果您有一個名為 的函數ls,則將呼叫該函數而不是程式ls。這可能很有用,除非您不小心這樣做了。

  • 變數的作用域是動態的,而不是像大多數現代語言那樣是靜態的。即使您可以有巢狀函數,內部函數也不會按照您通常期望的方式關閉外部函數的局部變數。你不能像人們用 Javascript 那樣使用它們來製作模組。

  • 茲什有匿名函數,但如果沒有其他東西,它們就沒有多大用處。

所以基本上,你能做的最好的事情就是為所有函數和全域變數加上前綴。

我還要指出你應該precmd這樣定義你的:

% autoload -Uz add-zsh-hook
% add-zsh-hook precmd my_precmd_function

add-zsh-hook讓您掛鉤您的函數,precmd而不會覆蓋任何其他可能也想掛鉤的函數precmd

載入函數意味著什麼是一個單獨的問題。 Zsh 具有自動載入功能,僅在實際呼叫函數時才從磁碟載入函數。當您這樣做時,您可以呼叫autoload -Uz foobar名為的函數。foobar當您實際呼叫 時foobar,會從磁碟載入定義。

答案2

不,閉包對於 zsh 來說太複雜了。 Zsh 旨在解釋與直接互動相距不遠的小腳本。它不具備對於大型程式設計非常有用的花哨語言功能,但對於 shell 通常用於執行的小型任務則不太有用。

請注意,如果存在某種形式的閉包,允許一次一勞永逸地預先計算變數的值然後存儲,那麼當某些變更導致資訊變得無效時,這些值將不會被更新。

$git_info由於對簽入 git 或 git 儲存庫的檔案進行修改,派生變數可以隨時變更。所以無論如何它們每次都需要重新計算。

cwd您可以將和的值緩存who_where在全域變數中,因為它們在正常操作下不會改變。cwd當前目錄更改時會更改,因此需要從chpwd.然而,這些變數的計算速度非常快,因此沒有必要費心。這里昂的計算正在運行git_prompt_info,並且可能隨時改變。

當您在每個命令之間顯示訊息時,最好將其作為提示(PS1psvar陣列)的一部分。 Zsh 知道它必須在各種情況下重新顯示提示,但它對您列印的內容一無所知precmd

答案3

是的,每次呼叫函數時都會(重新)定義這些變數。

如果您只想初始化它們一次,您可以簡單地將它們移至函數的頂層。

答案4

為了實現閉包,語言需要能夠將函數作為元素或物件進行操作,這在 zsh 中是不可能的,除非透過字串和內建函數eval(就像在其他 shell 中一樣)。但這是非常有限的,因為您需要自己處理所有低級的東西(例如引用)。然而,當參數沒有特殊字元時(因此不需要處理參考),使用這種想法很容易做一些簡單的事情,並且在 zsh 中,匿名函數可以提供一點幫助。例如,要定義計算 的函數a*x+b*y,其中ab是在函數定義時提供的常數,x並且y是函數的參數:

mk_ax_plus_by() { echo "() { echo \$((($1)*(\$1)+($2)*(\$2))) }" }

fct_2x_plus_3y=$(mk_ax_plus_by 2 3)
fct_5x_plus_7y=$(mk_ax_plus_by 5 7)

因此,有一個fct_2x_plus_3y計算函數2*x+3*y和一個fct_5x_plus_7y計算函數5*x+7*y(請注意,我選擇函數名稱只是為了方便閱讀,這些名稱可以是任何名稱,您甚至不需要將內容儲存在變數中)。另請注意,這些實際上是字串(不是 shell 術語中的函數),但它們的行為類似於eval內建函數。使用範例:

% eval $fct_2x_plus_3y 4 9
35
% eval $fct_5x_plus_7y 4 9
83

2*4+3*9給出 35 並給出5*4+7*983。

請注意,與函數式語言不同,這裡需要區分來自環境的參數(未加引號)和定義函數的參數(加引號),即僅在函數呼叫時評估。但是可以將實作更改為更像函數式語言,並且用法將是相同的:

mk_ax_plus_by()
{
  local x='$1' y='$2'
  echo "() { echo \$((($1)*($x)+($2)*($y))) }"
}

相關內容