我一直在慢慢地从 Bash 迁移到 Zsh,并且已经到了我所经历的一切都运行良好的地步,除了一个例外。
我的程序中有几个函数.bashrc
我每天使用数十次,其中两个函数在 Zsh 下无法工作。这三个功能构成了基本的笔记功能。
他们目前位于.config/zsh/functions
:
function n() {
local arg files=(); for arg; do files+=( ~/".notes/$arg" ); done
${EDITOR:-vi} "${files[@]}"
}
function nls() {
tree -CR --noreport $HOME/.notes | awk '{
if (NF==1) print $1;
else if (NF==2) print $2;
else if (NF==3) printf " %s\n", $3
}'
}
# TAB completion for notes
function _notes() {
local files=($HOME/.notes/**/"$2"*)
[[ -e ${files[0]} ]] && COMPREPLY=( "${files[@]##~/.notes/}" )
}
complete -o default -F _notes n
我的来源.zshrc
如下:
autoload bashcompinit
bashcompinit
# source zshrc functions file
source "$HOME/.config/zsh/functions"
nls
按预期工作,但既不完成n
也不Tab完成工作。
我读到man zshcompsys
它说:
函数 bashcompinit 提供了与 bash 可编程完成系统的兼容性。运行时,它将定义与具有相同名称的 bash 内置函数相对应的函数 compgen 和complete。然后就可以使用为 bash 编写的完成规范和函数。
然而,当我尝试 Tab完成时,没有任何反应,当我输入 时n notename
,Vim/home
在文件浏览器模式下打开我的文件 - 不完全是预期的行为。
所有其他定义的函数都运行良好。如何将这些功能迁移到 Zsh 下工作?
答案1
local
是内置函数,而不是关键字,因此local files=(…)
不会解析为数组赋值,而是解析为字符串赋值。将赋值与声明分开编写。 (已经由 llua 发现,但请注意,您需要初始化files
为空数组或使用 声明变量typeset -a
,否则数组以虚假的空元素开头。)- Zsh 数组从 1 开始编号,而不是像 bash 和 ksh 中那样从 0 开始编号,因此
${files[0]}
必须写成$files[1]
.或者,告诉 zsh 以与 ksh 和 bash 更兼容的方式运行:放在emulate -L ksh
函数的开头。 - 除非您走这
emulate
条路,否则如果 没有完成,您的_notes
函数将打印,因为默认情况下不匹配的 glob 会触发错误。添加zsh: no matches found: foo*
foo
全局限定符N
如果没有匹配则获取一个空数组,并测试数组是否为空。 - 您的函数中存在另一个错误,该错误
_notes
会影响子目录中的注释:您必须删除前缀直至完成,以便如果 eg~/notes/foo/bar
存在并且您键入n b<TAB>
,COMPREPLY
则设置为 containsb
,而不是foo/b
。
如果您想保留 bash 和 zsh 都可读的文件:
type emulate >/dev/null 2>/dev/null || alias emulate=true
function n() {
emulate -L ksh
local arg; typeset -a files
for arg; do files+=( ~/".notes/$arg" ); done
${EDITOR:-vi} "${files[@]}"
}
function nls() {
tree -CR --noreport $HOME/.notes | awk '{
if (NF==1) print $1;
else if (NF==2) print $2;
else if (NF==3) printf " %s\n", $3
}'
}
# TAB completion for notes
function _notes() {
emulate -L ksh
local x files
files=($HOME/.notes/**/"$2"*)
[[ -e ${files[0]} ]] || return 1
COMPREPLY=()
for x in "${files[@]}"; do
COMPREPLY+=("$2${x#$HOME/.notes*/$2}")
done
}
complete -o default -F _notes n
如果您想将代码移植到 zsh:
function n() {
local files
files=(${@/#/~/.notes/})
${EDITOR:-vi} $files
}
function nls() {
tree -CR --noreport $HOME/.notes | awk '{
if (NF==1) print $1;
else if (NF==2) print $2;
else if (NF==3) printf " %s\n", $3
}'
}
# TAB completion for notes
function _notes() {
setopt local_options bare_glob_qual
local files
files=(~/.notes/**/$2*(N))
((#files)) && COMPREPLY=($2${^files##~/.notes*/$2})
}
complete -o default -F _notes n
答案2
zsh 的 typeset(local) 命令无法使用其语法定义数组。您可以创建数组,但不能同时在一个命令中设置所有值。
function n() {
local arg files; for arg; do files+=( ~/.notes/$arg ); done
vim ${files[@]}
}
是修复它的一种方法。