如何處理 zsh 補全函數中包含單引號的檔名?

如何處理 zsh 補全函數中包含單引號的檔名?

我正在使用zshshell 並在我的 中包含以下程式碼.zshrc

fpath=(~/.zsh/completion $fpath)
autoload -Uz _vc

autoload -Uz compinit
compinit

第一行新增~/.zsh/completion儲存在環境變數中的陣列的路徑fpath

第二_vc行為名為 的自訂 shell 函數載入名為 的完成函數vc。的目的vc只是編輯特定資料夾 ( ~/.cheat) 內的文件。_vc在文件中定義~/.zsh/completion/_vc

最後兩行啟用完成系統。

這是我的完成功能的程式碼_vc

#compdef vc

declare -a cheatsheets
cheatsheets="$(ls ~/.cheat)"
_arguments "1:cheatsheets:(${cheatsheets})" && return 0

我是從這個複製的地址,並根據我的需要進行了調整。

只要目錄中~/.cheat沒有名稱包含單引號的文件,補全就會起作用。但如果有一個,例如foo'bar,則完成將失敗並顯示以下錯誤訊息:

(eval):56: unmatched '
(eval):56: unmatched '
(eval):56: unmatched '
(eval):56: unmatched '

我找到了一個解決方案,將行中的雙引號替換cheatsheets="$(ls ~/.cheat)"為單引號cheatsheets='$(ls ~/.cheat)'

現在,當我在命令Tab後點擊時vc, zsh 會建議 內的文件~/.cheat,包括foo\'bar(zsh 似乎已自動轉義單引號)。

但是,我不明白它是如何或為什麼起作用的。使用單引號,變數cheatsheets將包含一個文字字串。所以$(...)命令替換不應該被擴充。例如,如果我執行以下命令:

myvar='$(ls)'
echo $myvar    →    outputs `$(ls)` literally

那為什麼要'$(ls ~/.cheat)'在裡面進行擴充呢_arguments "1:cheatsheets:(${cheatsheets})" && return 0?為什麼它會自動轉義裡面的單引號foo'bar

答案1

如果我們堅持以錯誤的方式做事™

#compdef vc

declare -a cheatsheets
cheatsheets=(${(f)"$(ls ~/.cheat/)"})
_arguments '1:cheatsheets:(${cheatsheets})' && return 0

哎呀!如果檔案名稱包含換行符,這當然會中斷,因為(f)它們會被分割。解析ls無論如何都是個壞主意; ZSH 可以將 glob 檔案直接放入陣列中:

% mkdir ~/.lojban
% touch ~/.lojban/{go\'i,na\ go\'i}
% words=(~/.lojban/*) 
% print -l ${words:t}
go'i
na go'i
% words=(~/.lojban/*(On))
% print -l ${words:t}    
na go'i
go'i
% 

但我們可能不需要本地數組;_files可以在 glob 上完成:

#compdef vc
_arguments '1:cheatsheets:_files -g "~/.cheat/*"' && return 0

這將返回完全限定的檔案路徑;如果搜尋目錄中只需要裸檔名,我們可以使用:

#compdef vc
_arguments '1:cheatsheets:_files -W ~/.cheat' && return 0

相關內容