ZSH 启动慢,原来是这个问题!
ZSH 启动慢,原来是这个问题!
dong4j背景
事情的起因是 ComfyUI 官网出桌面版了, 虽然是 Bete 版本, 当还是准备试用一下, 结果第一步安装环境就卡住了:
The default interactive shell is now zsh. To update your account to use zsh, please run 'chsh -s bin/zsh'.
这个再熟悉不过了, 提示我们更新 shell 为 zsh, 但是我并不想更新, 我并不想把 zsh 用作我的默认 shell, 因为 zhs 的启动时间太长.
这个问题是在将老系统迁移到新买的 MBP 时出现的, 现象是将 zsh 作为默认 shell 后, 每次打开终端都需要等待 1-2 分钟, 才出现提示符. 这显然是不能接受的.
后来使用 Command
直接指定 /bin/zsh
就可以了, 也就没在深究这个问题.
但今天这个问题逃不过去了, 就开始研究一下, 彻底解决这个问题.
shell 配置加载顺序
因为现象是使用默认的 /bin/bash
, 然后直接是用 /bin/zsh
切换到 zsh 打开速度很快.
但是将 zsh 设置为系统默认 shell 时就会卡住, 且 Title 处会有多个 job 循环显示, 所以我猜测是 zsh 的配置文件加载顺序有问题.
在 Unix 和类 Unix 操作系统中,shell 有两种类型的会话:
interactive login shell
:这种类型的 shell 在 用户登录时被启动,通常是用户首次打开终端或连接到服务器时所见的 shell。login shell 负责加载与系统配置相关的文件,例如/etc/profile、~/.profile 等。这些文件用于设置环境变量、配置别名和函数、加载系统级别的 bash 插件等。interactive non-login shell
:这种类型的 shell 在 用户已经登录并且不涉及新会话的情况下启动,例如通过 bash -i 或通过某些图形界面工具打开的终端窗口。non-login shell 通常不会加载与登录相关的文件,而是从当前用户的.bashrc 或.zshrc(如果是使用 zsh)等个人配置文件中读取设置。
bash 配置加载顺序
对于 Bash,它们的工作原理如下。阅读相应的列。执行 A,然后执行 B,然后执行 C 等等。B1、B2、B3 表示它只执行找到的第一个文件。
config | interactive login shell | interactive non-login shell | script |
---|---|---|---|
/etc/profile |
A | ||
/etc/bash.bashrc |
A | ||
~/.bashrc |
B | ||
~/.bash_profile |
B1 | ||
~/.bash_login |
B2 | ||
~/.profile |
B3 | ||
BASH_ENV |
A | ||
~/.bash_logout |
C |
以 interactive login shell
为例:
- 首先读取
/etc/profile
- 然后加载
~/.bash_profile
,~/.bash_login
,~/.profile
三者中能找到的第一个配置; - 用户注销时执行
~/.bash_logout
(如果存在的话);
执行顺序图:
1. 是否为交互式 Shell (Interactive?)
- Yes:进入交互式模式,即用户可以在终端输入命令。
- No:非交互式模式,通常用于脚本执行。
2. 交互式模式下
是否为登录 Shell (Login shell?)
Yes(登录 Shell):
- 如果
--noprofile
参数被指定,则 不加载任何配置文件。 - 如果没有
--noprofile
:- 加载
/etc/profile
。 - 然后依次查找以下文件中的第一个存在的文件:
~/.bash_profile
~/.bash_login
~/.profile
- 这些文件可能包含对 ~/.bashrc 的引用。
- 加载
- 如果
No(非登录 Shell):
- 如果指定
--rcfile <file>
,则加载指定的配置文件。 - 如果没有指定 –rcfile:
- 没有
--norc
参数:加载/etc/bash.bashrc
和~/.bashrc
。 - 有
--norc
参数:不加载任何文件。
- 没有
- 如果指定
3. 非交互式模式下
- 如果指定
--login
参数,则加载/etc/profile
及登录 Shell 配置文件。 - 如果没有
--login
,但设置了$BASH_ENV
变量,则加载该变量指定的文件。
总结
- 登录 Shell 通常在用户首次登录时启动,会加载
/etc/profile
和用户特定的登录配置文件(如~/.bash_profile
)。 - 非登录 Shell 常用于终端内启动的子 Shell,主要加载
~/.bashrc
。 - 非交互式 Shell 主要用于脚本执行,加载
$BASH_ENV
指定的环境。
zsh 配置加载顺序
对于 zsh:如果~/.zshrc
不存在, zsh 似乎也会读取~/.profile
)
config | interactive login shell | interactive non-login shell | script |
---|---|---|---|
/etc/zshenv |
A | A | A |
~/.zshenv |
B | B | B |
/etc/zprofile |
C | ||
~/.zprofile |
D | ||
/etc/zshrc |
E | C | |
~/.zshrc |
F | D | |
/etc/zlogin |
G | ||
~/.zlogin |
H | ||
~/.zlogout |
I | ||
/etc/zlogout |
J |
总结
- 对于
bash
,请将内容放入~/.bashrc
中,然后使用~/.bash_profile
来获取它; - 对于
zsh
,将内容放入~/.zshrc
中,该操作始终执行;
问题排查
在清楚 bash 和 zsh 的配置加载顺序之后, 逐渐缩下了排查问题的范围, 现象是 zsh 作为默认终端加载慢(interactive non-login shell
), 而在命令行中执行 /bin/zsh
切换到 zsh 就很快, 所以应该是 /etc/zprofile
和 ~/.zprofile
配置的问题.
这是 /etc/zprofile
的配置:
1 | # System-wide profile for interactive zsh(1) login shells. |
应该没有什么问题, 注释说的是 ~/.zprofile
中的配置会覆盖这里的配置.
那么见证奇迹的时刻出现了, 当我打开 ~/.zprofile
后, 感觉不得不写一篇博客来记录一下:
这个文件中有 4000+ 行相同的 eval "$(/opt/homebrew/bin/brew shellenv)"
配置…….
而在 .zshrc
存在如下配置:
1 | ... |
那问题现在就明了了: 因为每次启动 zsh 最终都会读取 .zshrc
文件, 然后 .zshrc
又会向 .zprofile
添加一行 eval "$(/opt/homebrew/bin/brew shellenv)"
, 而第一次启动 zsh 的时候又要执行 .zprofile
里面几千行同样的命令…… 😂😂😂
eval "$(/opt/homebrew/bin/brew shellenv)"
的作用是将 Homebrew 的环境变量配置到当前的 Shell 中,使得 Homebrew 的命令和软件可以正常使用。具体来说:
/opt/homebrew/bin/brew shellenv
会输出一系列环境变量设置命令:
1 | export HOMEBREW_PREFIX="/opt/homebrew"; |
而 eval
会执行字符串形式的命令,将 brew shellenv 输出的环境变量立即加载到当前 Shell 环境中。
总结起来:
Homebrew 在 macOS 上默认安装在
/opt/homebrew
(Apple Silicon 或 Homebrew 自行编译的环境),为了让终端能够找到 brew 命令及其安装的软件,需要将其路径加入到 PATH 和其他环境变量中。直接执行 eval 命令可以立即生效,而无需重启终端或手动修改配置文件。
而在翻看 Homebrew 的 Discussions 正好看到一个 相关的讨论:
所以解决的办法就是删除 .zshrc
中的相关配置, 然后清理 .zprofile
.
macOS 设置 zsh 为默认 shell
从 macOS Catalina 开始,macOS 使用 zsh 作为默认登录 shell 和交互式 shell.
从命令行
在终端中,输入$ chsh -s path
,其中路径是 /etc/shells 中列出的 shell 路径之一,例如 /bin/zsh、/bin/bash、/bin/csh、/bin/dash、/bin/ksh、/bin/sh 或 /bin/tcsh。
从用户和群组设置
在 macOS Ventura 或更高版本中:
- 选择苹果菜单 >“系统设置”,然后单击边栏中的“用户与群组”。
- 按住 Control 键并点按右侧用户列表中的用户名或用户图片,然后选择“高级选项”。
- 出现提示时输入您的用户名和密码。
- 从“登录 shell”菜单中选择一个 shell,然后单击“确定”保存更改。
在早期版本的 macOS 中:
- 选择苹果菜单 >“系统偏好设置”,然后单击“用户与群组”。
- 单击锁,然后输入您的用户名和密码。
- 按住 Control 键并点按左侧用户列表中的用户名,然后选择“高级选项”。
- 从“登录 shell”菜单中选择一个 shell,然后单击“确定”保存更改。
参考:
zsh 启动时间优化
相关教程: