Clash 终端工作流续篇:为什么 cx off 了代理却“还在”
这篇是上一篇《Linux 上把 Clash 用顺:开启代理、切换节点、查询出口、脚本与日常工作流》的续篇。
上一篇我已经把下面这些基础能力打通了:
- 在 Linux 终端里开启/关闭当前 shell 代理
- 通过 Clash API 查看当前节点、列出节点、切换节点
- 用脚本把日常命令统一成
cx - 用
cx test、cx country验证代理是否真的可用
在实际使用时,我遇到的一个常见问题是:它很容易造成误解:
我明明已经执行了
cx off,为什么cx ports还能看到 7890/7891/9090 在监听?
为什么cx current还能返回当前节点?
为什么有时候cx test甚至还能通?
如果第一次做终端代理工作流,这个现象通常会让人误以为:
cx off没生效- 脚本写错了
- Clash 没有真正关闭
- 代理环境变量没有被清掉
其实都不是。
这篇文章就专门把这个问题讲透:
cx on、cx off、cx stop到底分别在控制什么- 为什么关掉“当前终端代理”之后,Clash 仍然会继续监听
- 为什么
cx current在cx off之后还能工作 cx test到底是在测“当前 shell 默认代理”,还是在测“Clash 端口本身”- 如何正确区分“关闭当前终端代理”和“彻底关闭 Clash 服务”
关键结论先行
cx off(通常等价于清理当前 shell 的代理环境变量)与“停止 Clash 服务”不是一回事。因此你可能会看到:代理被关闭了,但 7890/7891/9090 仍在监听,API 仍能返回当前节点信息,甚至某些显式走代理端口的测试仍能成功。
要避免误判,需要先明确你在验证的是哪一层:环境变量层、端口/服务层,还是 API/策略层。
前置条件
- Clash/Mihomo 服务可用,并启用了 external controller(通常监听在
127.0.0.1:9090)。 - 代理端口可监听:
7890(HTTP)7891(SOCKS5)9090(API)
- 你已实现类似
cx off/cx stop的命令,并能执行cx ports/cx current/cx test。
可复现步骤(建议顺序)
- 执行
cx off。 - 用
env | grep -i proxy检查当前 shell 的代理环境变量是否已清理。 - 执行
cx ports确认服务端口是否仍在监听。 - 执行
cx current验证 API/策略层是否仍可查询。 - 再分别用“依赖环境变量的请求”和“显式指定
--proxy http://127.0.0.1:7890”的方式做对比。
常见误解与纠正(本文后续展开)
常见误解是把“清理环境变量”误认为“停止服务”。后文会用对照表解释 cx on/off/stop 的边界,并给出更可靠的验证路径。
一、现象:cx off 之后,端口还在,节点还能查
我当时实际碰到的是这样一组输出:
1 | cx off |
第一眼看上去很矛盾:
- 不是已经
proxy disabled了吗? - 为什么端口还在监听?
- 为什么当前节点还能查到?
后来我才意识到,这里其实混了两层完全不同的东西:
- 当前 shell 的代理环境变量
- 后台运行的 Clash/Mihomo 服务本身
cx off 只会影响第一层,不会影响第二层。
二、cx off 到底关掉了什么
我这套 cx 函数里,cx off 本质上只是执行了这些操作:
1 | unset http_proxy https_proxy HTTP_PROXY HTTPS_PROXY |
也就是说,它做的事情比较明确:
把“当前终端会话”的代理环境变量删除掉。
这意味着什么?
意味着在这个 shell 里,下面这类“默认读取环境变量”的命令,不再自动走代理:
1 | curl -I https://www.google.com |
但是,这并不意味着:
- Clash 进程被杀掉了
- 7890/7891/9090 端口被关闭了
- 策略组信息消失了
- 本地 API 不可访问了
所以,cx off 后看到 Clash 还在监听,是完全正常的。
三、为什么 cx ports 仍然能看到 7890/7891/9090
因为 cx ports 检查的是系统当前是否有 Clash 在监听这些端口,而不是“当前 shell 是否还在默认走代理”。
例如:
1 | cx ports |
输出的是:
1 | 127.0.0.1:7890 |
这说明的是:
- Clash 进程还活着
- HTTP 代理端口还活着
- SOCKS5 代理端口还活着
- API 控制端口还活着
它跟 http_proxy、https_proxy 有没有被 unset,根本不是一回事。
所以:
cx off不会让cx ports变空。
如果你想让 cx ports 变空,那你要执行的是:
1 | cx stop |
四、为什么 cx current 在 cx off 后还能用
这也是一个特别容易误判的点。
很多人会下意识觉得:
- 代理关了
- 那么和 Clash 有关的命令也应该全都失效
其实不是。
cx current 的实现逻辑,是去访问本地 API:
1 | http://127.0.0.1:9090/proxies |
这是本机回环地址,不是在访问外网。
只要:
- Clash 还在运行
127.0.0.1:9090还在监听- external-controller 没关
那你就仍然可以查:
- 当前节点
- 节点列表
- 策略组
- 切换节点
也就是说:
cx current是否可用,取决于 Clash 的 API 服务是否还在,不取决于当前 shell 是否开启默认代理。
这也是为什么你会看到:
1 | cx off |
该结果并非异常现象,它符合当前命令的实现边界。
五、为什么 cx test 有时在 cx off 后还能通
这个点更容易让人混淆。
如果你把 cx test 理解成“测试当前 shell 默认代理是否打开”,那它在 cx off 后应该失败才对。
但我后面把 cx test 调整成了更可靠的实现:它不是完全依赖当前环境变量,而是显式指定走 7890 端口测试。
例如逻辑类似这样:
1 | curl --proxy http://127.0.0.1:7890 https://www.google.com |
这意味着它实际测试的是:
本地 Clash 的 HTTP 代理端口本身是否可用
而不是:
当前终端默认代理环境变量是否开启
所以,只要 Clash 还在监听 7890,这个测试就可能成功,即使你刚刚已经执行了:
1 | cx off |
因此,这里需要区分两类测试。
flowchart TD
Q["你要验证的是什么?"] -->|"默认命令是否会走代理"| PathEnv["看 env + 执行默认请求(不显式指定代理)"]
Q -->|"端口/服务是否可用"| PathPorts["显式指定代理端口(如 --proxy http://127.0.0.1:7890)"]
PathEnv --> R1["用于判断:默认代理是否生效"]
PathPorts --> R2["用于判断:本地代理端口是否可用"]
六、两种“代理测试”不是一回事
1. 测试当前 shell 是否默认走代理
这种测试依赖当前环境变量:
1 | env | grep -i proxy |
如果 cx off 之后,环境变量没了,而且 curl 又不能直连外网,那么这个请求通常会失败。
这类测试回答的是:
当前终端会不会默认走代理?
2. 测试 Clash 的本地代理端口是否可用
这种测试直接指定代理端口:
1 | curl --proxy http://127.0.0.1:7890 -I https://www.google.com |
即使 cx off 了,只要 Clash 还在监听 7890,它仍然可能成功。
这类测试回答的是:
Clash 服务本身有没有正常提供代理能力?
两类测试都需要关注,但你需要明确自己到底在测哪一个。
七、三个命令的真正边界:cx on、cx off、cx stop
这一节是本文需要重点关注的部分。
cx on
作用:
- 给当前 shell 设置代理环境变量
- 让当前终端中很多命令默认走
127.0.0.1:7890/7891
它不负责:
- 启动 Clash
- 切换节点
- 检查监听状态
所以正确顺序一般是:
1 | cx start |
而不是只执行 cx on。
cx off
作用:
- 删除当前 shell 的代理环境变量
- 让当前终端恢复成“不默认走代理”的状态
它不负责:
- 关闭 Clash 进程
- 关闭 7890/7891/9090
- 禁用本地 API
- 清除当前节点状态
所以看到 cx off 之后 cx current 还能查,是正常的。
cx stop
作用:
- 真正停止 Clash/Mihomo 进程
- 让 7890/7891/9090 监听消失
- 让本地 API 失效
执行:
1 | cx stop |
这时如果实现正常,cx ports 应该就不会再看到监听端口。
如果你这时候再执行:
1 | cx current |
通常就会失败,因为 API 服务已经没了。
八、一个清晰的对照表
| 命令 | 影响当前 shell 默认代理 | 影响 Clash 后台进程 | 影响 7890/7891/9090 | 影响 cx current |
|---|---|---|---|---|
cx on |
是 | 否 | 否 | 否 |
cx off |
是(关闭) | 否 | 否 | 否 |
cx stop |
否 | 是(停止) | 是(消失) | 是(会失效) |
这张表有助于减少常见误解,并便于定位问题所在的层级。
flowchart TD
CXon["cx on"] --> Env["Shell环境变量层(默认代理)"]
CXoff["cx off"] --> Env
CXstop["cx stop"] --> Ports["Clash进程与监听端口(7890/7891/9090)"]
Ports --> Api["API/控制层可用性(/proxies)"]
Api --> Query["cx current/list/switch/select"]
Env --> DefaultReq["默认请求是否走代理"]
九、正确的验证方式
如果你想确认 cx off 是否真的生效,不要只看 cx ports。
更合理的验证顺序是:
1. 看环境变量是否真的被清掉
1 | env | grep -i proxy |
如果没有输出,说明当前 shell 的默认代理已经关了。
2. 试一个默认依赖环境变量的请求
1 | curl -I https://www.google.com |
如果你本机不能直连外网,这时它通常会失败。
3. 再看 Clash 服务是不是还在
1 | cx ports |
如果它们还正常,那说明只是:
- 当前终端默认代理关了
- 但 Clash 服务仍然活着
这正是 cx off 设计上该有的行为。
十、我现在的实际使用习惯
这次把整个工作流打通以后,我后面基本是这样用的。
开始工作前
1 | cx start |
工作过程中换节点
1 | cx select |
或者:
1 | cx switch "日本Z02" |
想确认自己当前到底走哪
1 | cx current |
暂时不想让当前终端默认走代理
1 | cx off |
但 Clash 服务我不关,因为我还想随时切节点、查状态。
确实不需要 Clash 了,再彻底关掉
1 | cx stop |
这样逻辑会更清晰:
off是“当前 shell 层面”stop是“后台服务层面”
十一、基于排查过程的一点总结
在 Linux 终端里做代理工作流时,常见的混淆点是:
“关闭当前终端代理” 和 “关闭代理服务本身”
它们听起来很像,但其实是完全不同的两件事。
如果不把这层关系想清楚,就会不断出现这些误解:
- 为什么我都
off了还在监听? - 为什么我都
off了节点还能查? - 为什么我都
off了测试还可能成功? - 是不是脚本没写对?
实际上不是脚本错了,而是我们在脑子里把两层状态混成了一层。
一旦拆开来看:
- shell 环境变量是一层
- Clash 进程与监听端口是一层
- 本地 API 又是一层
整体行为会更易理解。
十二、结语
上一篇文章,我主要解决的是:
- 怎么把 Clash 在 Linux 上真正用起来
- 怎么把节点切换、查询、出口验证都脚本化
这一篇文章,我更想强调的是:
脚本能跑通只是起点,接下来需要明确每条命令作用的层级。
对我来说,这次主要收获并不是又增加了一个 cx 命令,而是把几类状态拆开后,排查会更有方向:
- 当前终端是否默认走代理
- Clash 服务是否在后台运行
- 本地 API 是否仍然可访问
- 测试命令到底在验证哪一层能力
当这些边界更清楚后,终端代理工作流通常会更一致,也更易维护。
如果你也在做 Linux 终端代理、远程开发、AI 工具安装、GitHub 拉仓库、节点切换这类事情,希望这篇续篇能帮你少走一点弯路。
附:我现在最常用的几条命令
1 | cx start |
记住一句话就够了:
cx off关的是当前终端默认代理,cx stop关的才是 Clash 服务本身。



