zoxide 本机实测:一万条记查询中 5.7ms0.10 版 import --from

上一篇写完 eza 之后预告过,这次实测 ajeetdsouza/zoxide(星数约 4.05 万,GitHub API 读于 2026-11-11)。

zoxide 自称「更聪明的 cd」:它记住你去过的目录,按「频率 + 最近」打分,之后 z foo 就能直接跳到最常用的那个 foo 目录,不用再敲一长串路径。它是 z / autojump 这一类工具的 Rust 版。这次我没在自己真实的 shell 历史上测,而是造了一棵假目录树、用 _ZO_DATA_DIR 把数据库隔离出来,在 bash 和 zsh 里各跑了一遍,顺手踩到了几个教程里很少提的坑。

本机实测 + AI 辅助整理。测量时间:2026-11-11(Asia/Shanghai)。单个命令的墙钟用 hyperfine(-N -w 3 -r 30),文内写 中位数 ;shell 函数(z、钩子)没法单独交给 hyperfine,改在同一个 bash 进程里循环 200 次取平均、重复 3 轮取中间值。数据库文件在页缓存里,属于「热缓存」成绩。


本机环境

text 复制代码
OS:     Debian 13 (trixie) x86_64,8 核
zoxide: 0.10.0(GitHub release,musl 静态二进制)
bash:   5.2.37
zsh:    5.9
fzf:    0.60(zi 交互选择要用)

安装:apt 这次没装上,release 一个文件搞定

text 复制代码
$ sudo apt-get install -y zoxide
E: Unable to locate package zoxide

本机 apt 源当天 apt-get update 一直返回 500,所以装不上。我用 curl 直接读了 Debian trixie 的软件包索引:里面其实有 zoxide 0.9.7-1+b1,比 GitHub 最新的 0.10.0 落后一个小版本------这个差别后面坑 1 会用到。直接下官方 release:

bash 复制代码
curl -sL -o zoxide.tar.gz \
  https://github.com/ajeetdsouza/zoxide/releases/download/v0.10.0/zoxide-0.10.0-x86_64-unknown-linux-musl.tar.gz
tar xzf zoxide.tar.gz      # 包里有 zoxide、man 手册、各 shell 补全
mkdir -p ~/.local/bin && cp zoxide ~/.local/bin/
zoxide --version           # zoxide 0.10.0

二进制 1233512 字节(约 1.2 MB),musl 静态链接,老系统也能直接跑。release 里还有现成的 .deb。macOS 一般 brew install zoxide。装完要在 shell 配置最后加一行:

bash 复制代码
eval "$(zoxide init bash)"   # zsh 换成 zoxide init zsh

zi 交互选择依赖 fzf,zsh 是我为了对比单独装的(这两个我是从 Debian 镜像 curl 下 .deb 再 dpkg -i 装的)。

测试素材

路径 / 变量 说明
tree/ 假目录树:projects/{foo-api,foo-web,blog/src}、work/foo/bar、work/bar/foo、work/client-a/docs、notes/中文笔记、dir with space、tmp/gone
big/ 100 组 × 200 个,共 20000 个空目录,用来造大数据库
_ZO_DATA_DIR 每组测试一个独立数据库目录,不碰真实 ~/.local/share/zoxide/db.zo
伪造的 z / autojump 数据 手写带时间戳的 .z 格式文件和 autojump.txt,测导入与时间衰减

先搞懂它怎么记、怎么打分

eval "$(zoxide init bash)" 会定义 z、zi 两个函数,并往 PROMPT_COMMAND 里挂一个 __zoxide_hook:每次出提示符时,如果当前目录变了,就执行一次 zoxide add 当前目录 。数据库默认在 ~/.local/share/zoxide/db.zo,_ZO_DATA_DIR 可以改,但必须是绝对路径:

text 复制代码
$ export _ZO_DATA_DIR=../db; zoxide query foo
zoxide: _ZO_DATA_DIR must be an absolute path

每次 add 让该目录的 rank +1,查询时按「rank × 最近访问系数」排序。新加一次就显示 4.0、加三次 12.0。时间系数我用伪造的 z 数据导入测出来(每条 rank 都是 10):

最近一次访问 query -ls 显示 系数
刚刚 40.0 ×4
2 小时前 20.0 ×2
2 天前 5.0 ×0.5
10 天前 2.5 ×0.25
40 天前 2.5 ×0.25

也就是 1 小时内 ×4、1 天内 ×2、1 周内 ×0.5、更久 ×0.25。一周没去的目录,分数直接打到 1/16,所以它对「最近在忙什么」非常敏感。

坑 1:0.10 版 import --from 直接报错,也不收文件路径

网上大多数迁移教程(包括 README 的旧版本)写的是 zoxide import --from=z 路径。在 0.10.0 上:

text 复制代码
$ zoxide import --from=z fixtures/import/z.txt
error: unexpected argument '--from' found

$ zoxide import z fixtures/import/z.txt
error: unexpected argument '.../z.txt' found

CHANGELOG 写得很清楚:0.10.0 把 import 改成子命令(atuin / autojump / fasd / z / z.lua / zsh-z),并且自动去默认位置找数据库,不再接受路径参数。要导入非默认位置的文件,得用原工具自己的环境变量。我实测能用的写法:

bash 复制代码
_Z_DATA=/path/to/.z zoxide import z          # z 的数据默认在 ~/.z
HOME=/某个假 home zoxide import autojump     # 读 ~/.local/share/autojump/autojump.txt
zoxide import autojump --merge               # 数据库非空时必须加 --merge

数据库不为空时不加 --merge 会拒绝:current database is not empty, specify --merge to continue anyway。

版本差异是重点 :Debian trixie 仓库里是 0.9.7,按 CHANGELOG 还是旧的 --from 写法(这个我没装 0.9.7 验证)。apt 装的和 release 装的,同一条导入命令可能一个能用一个报错,先 zoxide import --help 看一眼。

导入的另外两个细节:

  • z 导入会带进不存在的目录 :我故意放了一条 /does/not/exist|50,导入后 -a 能看到它 200 分排第一;正常查询时它被隐藏,不影响跳转,但数据库里一直留着。
  • autojump 的权重被压平了 :我写的权重 30 / 1.0 / -3,导入后分别显示 0.2 / 0.2 / 0.0。权重被压进 0~1 再按「很久以前」处理,原来的先后顺序基本丢了,你在 zoxide 里正常 cd 进去一次(4.0)就能盖过它们。

坑 2:最后一个关键词必须命中「最后一段」目录名

数据库里有 projects/foo-api、projects/foo-web、work/foo/bar、work/bar/foo、projects/blog/src 等,下面全是本机输出(<T> 是测试树的根):

text 复制代码
$ zoxide query foo         → <T>/projects/foo-api    (分数最高)
$ zoxide query foo bar     → <T>/work/foo/bar
$ zoxide query bar foo     → <T>/work/bar/foo        (关键词顺序有意义)
$ zoxide query foo api     → <T>/projects/foo-api
$ zoxide query api foo     → no match found
$ zoxide query proj        → no match found
$ zoxide query blog        → no match found
$ zoxide query blog src    → <T>/projects/blog/src
$ zoxide query FOO         → <T>/projects/foo-api    (不分大小写)
$ zoxide query 中文        → <T>/notes/中文笔记

规则是:关键词按顺序出现在路径里,最后一个关键词必须出现在路径最后一段 。所以 z proj 跳不到 projects/foo-api,z blog 也去不了 blog/src------除非 projects、blog 这一层本身也进过数据库。

更隐蔽的是前面的关键词匹配的是整个绝对路径 。我的测试树放在 /workspace/... 下面:

text 复制代码
$ zoxide query work foo    → <T>/projects/foo-api

我本意是「work 下的 foo」,结果它匹配了路径开头 /workspace 里的 work,跳到了分数更高的 projects/foo-api。上层路径里的词越常见(home、work、src),多关键词就越容易「歪」。想要精确,最后一个关键词写得具体一点,或者用 zi 选。

还有一条优先级:参数本身是个能 cd 进去的路径时,z 就是普通 cd 。在测试树根目录 z work 进的是 ./work,不查数据库;换到 /tmp 再 z work,才会按数据库跳。

坑 3:bash 只在出提示符时记录,脚本里根本不记

同样一行连 cd 三次:

bash 复制代码
cd projects/foo-web; cd projects/blog/src; cd work/bar/foo
shell 数据库里新增
bash(默认 --hook pwd) 只有 work/bar/foo 1 条
zsh 3 条全记了

原因是 bash 版钩子挂在 PROMPT_COMMAND 上,只看出提示符那一刻的目录;zsh 版挂在 chpwd_functions,每次切目录都触发。另外 bash 钩子里有个 [[ -o history ]] 判断,set +o history 时进的目录不记(CHANGELOG 里 0.10.0 新增的行为;本机关掉 history 后分两行 cd 进 notes/中文笔记、projects/blog,都没进库,重新打开后进的 work/client-a 正常记录)。

非交互脚本更彻底:

text 复制代码
$ bash script.sh
script.sh: line 3: z: command not found        # 脚本不读 ~/.bashrc
(脚本里 eval init 之后连 cd 两次)→ 数据库仍为空
(手动调用 __zoxide_hook)            → 仍为空,非交互 bash 默认关着 history

结论:zoxide 只学习你在交互终端里的足迹,cron、CI、Makefile 里的 cd 不会污染排名;反过来脚本里也别指望 z,写全路径。

--hook prompt 则是另一个极端:每次出提示符都 +1。我 cd 进 foo-web 后敲了 4 次 true,它就变成 20.0 分(5 次 × 4),在一个目录里待得久的就会被抬得很高。

坑 4:_ZO_MAXAGE 不是天数

名字像「最长保留天数」,实际是所有条目 rank 总和的上限 (默认 10000)。超过后所有 rank 按比例压到约 0.9 × 上限,压完小于 1 的条目直接删除。_ZO_MAXAGE=100 实测:

text 复制代码
(rank 60 / 30 / 5 / 1 四条,总和 96)
 240.0 projects/foo-api
 120.0 projects/foo-web
  20.0 work/foo/bar
   4.0 notes
$ zoxide add -s 10 work/bar/foo        # 总和 106 > 100,触发老化
 203.8 projects/foo-api
 101.9 projects/foo-web
  34.0 work/bar/foo
  17.0 work/foo/bar
                                       # notes 被删了

压缩比例 203.8/240 ≈ 0.85 = 90/106,对得上。

极端情况我也测了:默认上限下,用脚本把 10000 个目录各 zoxide add 一次(每条 rank 1,总和正好 10000),再 add 第 10001 个------数据库从 10000 条变成 0 条 ,全部被压到 0.9 以下删光。正常人手动 cd 很难走到这一步,但如果你写脚本批量 zoxide add(比如把项目目录全灌进去),要么调大 _ZO_MAXAGE,要么用 -s 给高分。

坑 5:别的工具改写 PROMPT_COMMAND,zoxide 就「失忆」

init 之后如果别的配置把 PROMPT_COMMAND 整个覆盖(我用 PROMPT_COMMAND='history -a' 模拟),钩子就没了,之后 cd 不再进库。好在 0.10.0 有自检,第一次跑 z 会提示:

text 复制代码
zoxide: detected a possible configuration issue.
Please ensure that zoxide is initialized right at the end of your shell configuration file (usually ~/.bashrc).

这就是 README 反复强调「init 放在配置文件最后」的原因。看到这条提示,先检查后面有没有提示符美化工具在改 PROMPT_COMMAND。

卖点:用顺手之后确实回不去

  • 多关键词 + 打分 :z client docs 直接到 work/client-a/docs,z - 回上一个目录,z 不带参数回 home。
  • 不重复跳当前目录 :在 foo-api 里再 z foo,会跳到第二名;只有一个匹配时提示 you are already in the only match。
  • --cmd cd 直接接管 cd :zoxide init bash --cmd cd 后 type cd 显示 cd is a function,cd foo 按数据库跳,cd ..、cd 全路径 照旧,交互选择变成 cdi,z 不再定义。区别在于找不到时报 zoxide: no match found(exit 1),而不是 bash 原来的 No such file or directory;cd - 也不像原版那样打印目录。原版 cd 用 \builtin cd。
  • zi + fzf :没装 fzf 时 zoxide query -i 报 could not find fzf, is it installed?;stdin 不是终端时报 fzf returned an error,所以 zi 只适合交互用。
  • 自动排除 / 自愈 :$HOME 默认不进库(zoxide add ~ 无报错但查不到);不存在的目录 add 会报 not a directory。目录删掉后查询自动跳过它,但 query -lsa 看得到它还在库里,重新建回来又能跳;想彻底清用 zoxide remove 路径。_ZO_EXCLUDE_DIRS 支持 glob,'.../work/*' 能排除子目录但不排除 work 本身。
  • 中文、空格 :z 中文、z with 都能正确跳到 notes/中文笔记、dir with space。

速度:比 cd 慢几毫秒,人感觉不到

单条 zoxide 命令(hyperfine 中位数;数据库是 big/ 里的真实目录,hit 查 g00 proj050):

数据库条目 db.zo 大小 query 命中 query 无匹配 query -l 全列 add 已有目录
100 9.6 KB 0.98 ms 0.99 ms 1.06 ms 0.77 ms
1000 96 KB 1.29 ms 1.61 ms 2.61 ms 1.11 ms
10000 960 KB 5.69 ms 5.92 ms 16.18 ms 3.99 ms
20000 1.9 MB 9.69 ms 9.08 ms 33.10 ms 7.46 ms

参照:true 0.52 ms、zoxide --version 0.44 ms,所以 1000 条以内基本就是进程启动开销。耗时随条目数大致线性增长,-l 要逐条检查目录是否还存在,最慢。

和 cd 比(同一个 bash 进程里循环,3 轮中间值):

操作 库 100 条 库 10000 条
builtin cd 全路径 往返两次 0.013 ms 0.014 ms(另一轮最低 0.012)
z g00 proj050 + 一次 cd 回来 2.29 ms 5.41 ms
钩子:目录没变(每次出提示符) 0.46 ms 0.51 ms
钩子:目录变了(含一次 zoxide add) 约 2.2 ms 约 5.0 ms

钩子在目录没变时也要 0.5 ms,是因为 bash 版每次都用 $(pwd) 开一个子 shell 取当前目录;目录变了才真正调用 zoxide。启动开销:

命令 中位数
bash --norc -c true 1.13 ms
bash --norc -c 'eval "$(zoxide init bash)"' 3.51 ms
zsh -f -c true 1.32 ms
zsh -f -c 'eval "$(zoxide init zsh)"' 2.52 ms

zoxide init bash 本身只要 0.47 ms,多出来的主要是 bash 解析那 181 行脚本。结论:每开一个终端多 1~2.4 ms,每次 cd 多几毫秒,都在人感知不到的范围里;条目真涨到上万,z 也只是 5~10 ms。

我会怎么配

bash 复制代码
# ~/.bashrc 最后一行(放在提示符美化工具之后)
export _ZO_EXCLUDE_DIRS="$HOME:/tmp/*"
eval "$(zoxide init bash)"          # 保留 z/zi,不接管 cd,脚本行为不变

# 从 z 迁移(0.10 写法,旧版是 --from)
# _Z_DATA=~/.z zoxide import z --merge

我不用 --cmd cd:报错信息和 cd - 行为都变了,别人在我机器上敲 cd 会困惑;z 两个字母本来就够短。

小结

项 本机结果
版本 / 星数 zoxide 0.10.0 ,约 4.05 万 星(2026-11-11)
安装 apt 未装上(Debian 索引里是 0.9.7)→ GitHub release → ~/.local/bin,1.2 MB 单文件
打分 rank × 时间系数:1 小时内 ×4、1 天内 ×2、1 周内 ×0.5、更久 ×0.25
最大的坑 0.10 版 import 改成子命令、自动找文件,--from 和路径参数都报错
匹配规则 最后一个关键词必须命中最后一段;前面的词会匹配到上层 /workspace
记录时机 bash 只记提示符那一刻(一行 3 次 cd 记 1 条),zsh 记 3 条;脚本不记
_ZO_MAXAGE 是总分上限不是天数;默认上限下 10000 条 rank 1 再 add 一次 → 清空
速度 一万条 query 中位 5.69 ms;钩子每次提示符 0.5 ms;init 让 bash 启动 +2.4 ms

一句话:zoxide 是那种装上就忘了它存在、但每天都在省时间的工具。真正要注意的不是速度,而是它「什么时候记」和「怎么匹配」:最后一个词要写目录名本身,脚本里别用 z,从旧工具迁移先看你装的是哪个版本。

完整测量日志在本机 zoxide/measurements.txt、bench-*.json 与 feat-*.txt;完整安装脚本包和进阶踩坑表以后会整理上架 Gumroad,先关注专栏「GitHub实测笔记」。

上一篇:eza 本机实测(一万文件 -l 中位 48ms / ls 21.5ms)。

下一篇预告:junegunn/fzf------这篇里 zi 靠的就是它,实测它在大文件列表里的过滤速度、和 shell 的 Ctrl-R / Ctrl-T 集成踩不踩坑。有问题评论区见,看到都会回。

相关推荐
Brookty2 小时前
Git与GitHub系统学习|本地Git工作模型:终端与进程、CWD、HEAD/Index/工作树及Git对象
git·github
miofly18 小时前
Genie 4新增原生音频,实时世界模型加入声音生成
开源·github
dong_junshuai19 小时前
每天一个开源项目#155 MuJoCo:从接触求解到控制时序的物理引擎
github
GitHub实测笔记19 小时前
eza 本机实测:一万文件 -l 中位 48ms、ls 21.5ms;脚本里裸跑 eza 输出为空
github
一条Go19 小时前
开源新生代雷达 · 第 04 期
github
一条Go19 小时前
告警指纹:一次故障为什么只产生一条 FIRING 记录
后端·架构·github
wyxspark19 小时前
在本地 AI 中实现绝对可复现:一份 12,810 条哈希的硬核工程记录
github
简创AIGC陶先生19 小时前
【直播切片工作台】第31章:任务接口与任务服务
github
用户2991422196801 天前
GitHub 热榜项目:日榜(2026-11-10)
人工智能·开源·github