我一直用的 Zsh + Oh My Zsh,但是听说了 Fish 在这方面也做的很出现,所以打算尝试使用下 Fish。
为什么是 Fish
之前用 Zsh + Oh My Zsh,得装 zsh-autosuggestions、zsh-syntax-highlighting 这些插件,再配主题,折腾不少。Fish 把这些都内置了——自动补全、语法高亮,装完就有。
裸的 Zsh 和裸的 Fish 对比,Fish 体验差距挺明显。自动补全会扫系统的 Man Pages,按 Tab 不光补全参数,旁边还显示参数说明;历史记录预测用灰色字给出建议,按 → 采纳,命中率不错。语法高亮也是实时的,命令打错立刻变红,不用等回车才发现。
Zsh 插件装多了启动会慢,有些插件是 Shell 脚本写的,效率不高。Fish 的补全和高亮内置在底层,功能全开也不影响响应速度。
语法方面 Fish 抛弃了 POSIX 兼容,换来更干净的表达。比如设置环境变量:
1 | # Zsh / Bash |
1 | # Fish |
If 判断也没有 fi、esac 这种反写关键字,统一用 end 收尾:
1 | # Zsh / Bash |
1 | if test "$foo" = "bar" |
还有一个 fish_config 命令,终端里敲一下就在浏览器里打开配置页面,颜色、主题都能点选,不用手改配置文件。
不兼容 POSIX 是最大的代价,后面迁移过程中踩的坑基本都跟这个有关。
装上 Fish
macOS 直接 Homebrew 装:
1 | brew install fish |
装完确认一下:
1 | fish --version |
which fish 输出的路径后面配 Ghostty 要用到,记一下。也可以直接在当前终端输入 fish 回车,看到欢迎语和灰色自动补全就说明没问题了。
设置默认 Shell 的两种方式
装完之后怎么让终端默认用 Fish,有两条路。
第一种是直接改终端模拟器的配置,让新建窗口时跑 Fish 而不是系统默认 Shell。我用的 Ghostty,配置很简单:
1 | # 用 ghostty +edit-config 打开配置文件,加一行: |
路径用 which fish 查一下,不同机器可能不一样。
这种方式的好处是系统底层的 Zsh/Bash 不受影响,SSH 登录也还是原来的 Shell,不会踩到 PATH 或 profile 加载的坑。
第二种是把 Fish 设为登录 Shell:
1 | # 先加到 /etc/shells |
这样所有终端登录(包括 SSH)都会直接进 Fish。不过有些 Linux 发行版要求登录 Shell 必须兼容 Bourne 并读取 /etc/profile,Fish 在这些系统上可能会出问题。我选的是第一种,稳妥一些。
把 .zshrc 翻译成 Fish 语法
Fish 的语法比 Zsh 简洁不少,不需要等号赋值那一套。配置文件在 ~/.config/fish/config.fish:
1 | # 环境变量 |
改完 config.fish 保存后,两种方式让配置生效:偷懒的话直接 source ~/.config/fish/config.fish,当前窗口立刻生效。不过配置里用了 if status is-interactive,Starship、Zoxide 这些工具的初始化流程用 source 不一定完全干净,关掉终端重开一个窗口更稳妥。
迁移的时候遇到一个 sed 的坑。之前 Zsh 里有个 precmd 脚本用到了 sed -n "{$var}p" 这种写法,在 Fish 里直接报 expects up to 0 address(es), found 1。原因是 Fish 会原样解析大括号,改成拼接写法 sed -n "$var"p 就好了。
Zsh 历史记录导入 Fish
Fish 的历史记录格式跟 Zsh 完全不一样,不能直接搬。写了个 Python 脚本做转换:
1 | import os, re |
跑完重启终端,Fish 就能基于之前的 Zsh 记录做灰色自动补全了。
SDKMAN 的 Bash 兼容性问题
SDKMAN 的初始化脚本是给 Bash/Zsh 写的,Fish 没法直接 source。需要先装 Fish 的包管理器 Fisher,再通过它装 sdkman-for-fish 插件:
1 | # 安装 Fisher |
装完之后 sdk 命令基本能用了,但在 macOS 上还可能碰到一个报错:
1 | /Users/caratacus/.sdkman/src/sdkman-path-helpers.sh: line 61: ${candidate_name^^}: bad substitution |
一开始以为是 Fish 的锅,看了下发现不是——^^ 是 Bash 4.0 才有的变量转大写语法,而 macOS 自带的 Bash 还停在 3.2。插件底层还是会调系统自带的 Bash 执行脚本,照样报错。
直接改 SDKMAN 的源码,把 ^^ 替换成兼容的写法:
1 | # 修改前: |
改完 sdk 命令就正常了。缺点是每次更新 SDKMAN 都得重新改一遍,不过也就一行的事。
整体下来,从 Zsh 切到 Fish 最大的成本就是配置迁移和那些基于 Bash 的工具链适配。POSIX 不兼容这个事见仁见智,对我来说日常交互用 Fish,脚本跑 Bash,各司其职,没什么冲突。Fish 的自动补全和语法高亮确实比 Zsh 舒服不少,配好之后基本不用再折腾配置了。