如何处理 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

相关内容