Zsh/NVM排查
从一个打不开的桌面图标说起
这次想通过桌面图标启动本地的 DeepSeek Harness。启动器 deepseek_harness.desktop 直接调用 deepseek_harness.sh,但点击图标后无法正常启动,于是先怀疑是不是脚本不适合从桌面执行。
下面只保留排查所需的信息,本机用户名、挂载点和目录结构均已隐去。使用 ./ 的命令假定已经进入相应文件所在目录。
原来的脚本只有两行:
1 | |
其中 npx @deepseek-ai/dsh web 就是 DeepSeek 官方提供的启动命令。桌面启动器中与本次排查有关的部分配置如下,实际 Exec 和图标路径已省略:
1 | |
看起来已经指定了脚本位置,也要求在终端中运行,问题究竟出在哪里?
先检查路径、权限和语法
启动器中的脚本路径与我平时使用的路径看起来不同,不过其中一个目录是符号链接。检查实际指向后,确认它们对应同一个文件,路径没有写错。排查时可以用 readlink -f ./deepseek_harness.sh 查看脚本的实际位置,不必在公开记录中保留具体的挂载目录。
脚本本身也有执行权限,并且通过了语法检查:
1 | |
所以,仅仅把路径换一种写法,或者再执行一次 chmod +x,都没有解决这次问题的关键。
真正缺少的是 NVM 环境
本机的 Node.js 是通过 NVM 安装的,npx 位于 NVM 管理的版本目录中。隐去具体版本后,路径结构如下:
1 | |
NVM 的初始化配置则写在 ~/.zshrc 中:
1 | |
这里有一个容易忽略的区别:桌面启动器执行脚本时,得到的环境不一定与平时打开终端后使用的环境相同。
检查实际会话环境后发现,GNOME Shell、文件管理器和终端服务的 PATH 都没有上述 NVM 的 Node.js 目录,也都找不到 npx。原脚本又由 Bash 直接执行,不会去读取我的 .zshrc,自然无法补上这个路径。
在不加载 NVM 的干净环境中运行原脚本,可以复现下面的报错(文件路径已简化):
1 | |
退出码为 127。也就是说,脚本还没执行到 DeepSeek Harness 本身,就已经因为找不到 npx 退出了。
Terminal=true 只负责让程序在终端窗口中运行,不会自动保证这个程序先经过交互式 Zsh 的初始化流程。终端窗口与 Shell 解释器需要分开理解。
几种处理方式
最直接的做法,是在 Bash 脚本里显式加载 NVM:
1 | |
这种方式只加载所需的 NVM 配置,不必把整个 .zshrc 都执行一遍。验证时,加载 NVM 后可以正确找到 node 和 npx。
不过我更想沿用已经配置好的 Zsh 环境,因此又考虑先运行 Zsh,再调用原脚本。进入脚本所在目录后,核心命令可以写成:
1 | |
桌面启动器也可以采用这一思路,不过使用相对路径时,需要另行保证它的工作目录就是脚本所在目录,不能直接假定桌面启动时的当前目录。
其中 -i 指定交互模式,-c 指定执行后面的命令。Zsh 先读取 .zshrc、初始化 NVM,再执行原来的 Bash 脚本。虽然脚本仍由 Bash 解释,但它会继承 Zsh 已经设置好的 PATH。
在干净环境中通过 zsh -ic 检查,得到的结果如下,第一行中的目录和版本目录名已泛化:
1 | |
三行分别对应 command -v npx、node --version 和 npx --version,说明这条初始化链路可以找到本机的 Node.js 环境。
能不能只修改脚本第一行?
进一步想了一下:既然是需要先进入 Zsh,能不能直接在脚本第一行指定,而不用修改桌面启动器?
可以。针对当前这份脚本,可以写成:
1 | |
第一行称为 shebang,用于在直接执行脚本时指定解释器。在本机 Linux 环境下,这里指定的是 /bin/zsh,并向它传入 -i 参数。
单独写 #!/bin/zsh 还不够,关键在于后面的 -i。
-i 是 interactive,即强制交互模式。交互式 Zsh 会读取 .zshrc,从而执行我放在其中的 NVM 初始化配置,再运行脚本里的 npx 命令。它并不要求脚本接下来一定要等待人工输入。
这里也需要区分几个概念:
| 配置或参数 | 作用 |
|---|---|
Terminal=true |
让桌面启动器在终端窗口中运行程序 |
#!/bin/zsh |
直接执行脚本时使用 Zsh 解释 |
-i |
强制交互模式,使 Zsh 读取 .zshrc |
.zshrc 中的 NVM 配置 |
加载 NVM,设置所选 Node.js 版本的环境 |
因此,-i 本身不负责激活 NVM,也不负责打开终端窗口;它让 Zsh 读取配置,再由配置完成 NVM 初始化。
这样写时,启动器原来的 Exec 可以保持不变。不过要让 shebang 生效,必须直接执行脚本,例如:
1 | |
如果改用 sh 脚本路径 或 bash 脚本路径,解释器就由命令中的 sh 或 bash 决定,第一行指定的 Zsh 不会生效。
这种方案会顺带加载 .zshrc 中的主题、插件及其他初始化配置。对于当前这个两行的个人启动脚本,可以沿用已有环境;如果换成内容较多的 Bash 脚本,修改解释器前还需要检查其中的语法是否适用于 Zsh。
另外发现的启动器问题
使用下面的命令校验桌面启动器:
1 | |
还发现了一个分类字段错误:
1 | |
Terminal 不是已注册的分类值,可以改为:
1 | |
这是启动器需要修正的另一个问题,但前面复现到的 npx: command not found,原因仍然是缺少 NVM 环境。
记录时,本地脚本的第一行已经改为 #!/bin/zsh -i。本次验证确认了桌面会话缺少 NVM 路径,以及交互式 Zsh 能加载配置并找到 npx;没有实际启动 DeepSeek Harness 服务来验证网页是否打开,因此这里保留这一验证范围。
参考资料
- DeepSeek Harness:官方启动方式
- freedesktop:Desktop Entry Specification
- Bash:启动文件的读取规则
- Zsh:启动参数
- Zsh:启动文件的读取规则
- NVM:非交互式环境下的初始化说明
封面使用 Justin Dorfman 设计的 Zsh 标志,按 CC BY-SA 4.0 许可使用,本文未修改该图片。




