这篇是上一篇《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 没有真正关闭
  • 代理环境变量没有被清掉

其实都不是。

这篇文章就专门把这个问题讲透:

  1. cx on、cx off、cx stop 到底分别在控制什么
  2. 为什么关掉“当前终端代理”之后,Clash 仍然会继续监听
  3. 为什么 cx current 在 cx off 之后还能工作
  4. cx test 到底是在测“当前 shell 默认代理”,还是在测“Clash 端口本身”
  5. 如何正确区分“关闭当前终端代理”和“彻底关闭 Clash 服务”

关键结论先行

cx off(通常等价于清理当前 shell 的代理环境变量)与“停止 Clash 服务”不是一回事。因此你可能会看到:代理被关闭了,但 7890/7891/9090 仍在监听,API 仍能返回当前节点信息,甚至某些显式走代理端口的测试仍能成功。

要避免误判,需要先明确你在验证的是哪一层:环境变量层、端口/服务层,还是 API/策略层。

前置条件

  1. Clash/Mihomo 服务可用,并启用了 external controller(通常监听在 127.0.0.1:9090)。
  2. 代理端口可监听:
    • 7890(HTTP)
    • 7891(SOCKS5)
    • 9090(API)
  3. 你已实现类似 cx off/cx stop 的命令,并能执行 cx ports/cx current/cx test。

可复现步骤(建议顺序)

  1. 执行 cx off。
  2. 用 env | grep -i proxy 检查当前 shell 的代理环境变量是否已清理。
  3. 执行 cx ports 确认服务端口是否仍在监听。
  4. 执行 cx current 验证 API/策略层是否仍可查询。
  5. 再分别用“依赖环境变量的请求”和“显式指定 --proxy http://127.0.0.1:7890”的方式做对比。

常见误解与纠正(本文后续展开)

常见误解是把“清理环境变量”误认为“停止服务”。后文会用对照表解释 cx on/off/stop 的边界,并给出更可靠的验证路径。

一、现象:cx off 之后,端口还在,节点还能查

我当时实际碰到的是这样一组输出:

1
2
3
4
5
6
7
8
9
10
cx off
[cx] proxy disabled

cx ports
LISTEN 0 4096 127.0.0.1:7891 0.0.0.0:* users:(("clash",pid=21664,fd=9))
LISTEN 0 4096 127.0.0.1:7890 0.0.0.0:* users:(("clash",pid=21664,fd=8))
LISTEN 0 4096 127.0.0.1:9090 0.0.0.0:* users:(("clash",pid=21664,fd=11))

cx current
🇺🇲 美国Z01

image-20260319092531793

第一眼看上去很矛盾:

  • 不是已经 proxy disabled 了吗?
  • 为什么端口还在监听?
  • 为什么当前节点还能查到?

后来我才意识到,这里其实混了两层完全不同的东西:

  1. 当前 shell 的代理环境变量
  2. 后台运行的 Clash/Mihomo 服务本身

cx off 只会影响第一层,不会影响第二层。


二、cx off 到底关掉了什么

我这套 cx 函数里,cx off 本质上只是执行了这些操作:

1
2
3
unset http_proxy https_proxy HTTP_PROXY HTTPS_PROXY
unset all_proxy ALL_PROXY
unset no_proxy NO_PROXY

image-20260319092715904

也就是说,它做的事情比较明确:

把“当前终端会话”的代理环境变量删除掉。

这意味着什么?

意味着在这个 shell 里,下面这类“默认读取环境变量”的命令,不再自动走代理:

1
2
3
4
curl -I https://www.google.com
git ls-remote https://github.com/git/git.git | head
pip install requests
npm view react version

但是,这并不意味着:

  • Clash 进程被杀掉了
  • 7890/7891/9090 端口被关闭了
  • 策略组信息消失了
  • 本地 API 不可访问了

所以,cx off 后看到 Clash 还在监听,是完全正常的。


三、为什么 cx ports 仍然能看到 7890/7891/9090

因为 cx ports 检查的是系统当前是否有 Clash 在监听这些端口,而不是“当前 shell 是否还在默认走代理”。

例如:

1
cx ports

输出的是:

1
2
3
127.0.0.1:7890
127.0.0.1:7891
127.0.0.1:9090

这说明的是:

  • 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
2
3
cx off
cx current
🇺🇲 美国Z01

该结果并非异常现象,它符合当前命令的实现边界。


五、为什么 cx test 有时在 cx off 后还能通

这个点更容易让人混淆。

如果你把 cx test 理解成“测试当前 shell 默认代理是否打开”,那它在 cx off 后应该失败才对。

但我后面把 cx test 调整成了更可靠的实现:它不是完全依赖当前环境变量,而是显式指定走 7890 端口测试。

例如逻辑类似这样:

1
2
curl --proxy http://127.0.0.1:7890 https://www.google.com
curl --proxy http://127.0.0.1:7890 https://api.ip.sb/ip

这意味着它实际测试的是:

本地 Clash 的 HTTP 代理端口本身是否可用

而不是:

当前终端默认代理环境变量是否开启

所以,只要 Clash 还在监听 7890,这个测试就可能成功,即使你刚刚已经执行了:

1
cx off

因此,这里需要区分两类测试。


image-20260319093001768

flowchart TD
  Q["你要验证的是什么?"] -->|"默认命令是否会走代理"| PathEnv["看 env + 执行默认请求(不显式指定代理)"]
  Q -->|"端口/服务是否可用"| PathPorts["显式指定代理端口(如 --proxy http://127.0.0.1:7890)"]
  PathEnv --> R1["用于判断:默认代理是否生效"]
  PathPorts --> R2["用于判断:本地代理端口是否可用"]

六、两种“代理测试”不是一回事

1. 测试当前 shell 是否默认走代理

这种测试依赖当前环境变量:

1
2
env | grep -i proxy
curl -I https://www.google.com

如果 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
2
cx start
cx on

而不是只执行 cx on。


cx off

作用:

  • 删除当前 shell 的代理环境变量
  • 让当前终端恢复成“不默认走代理”的状态

它不负责:

  • 关闭 Clash 进程
  • 关闭 7890/7891/9090
  • 禁用本地 API
  • 清除当前节点状态

所以看到 cx off 之后 cx current 还能查,是正常的。


cx stop

作用:

  • 真正停止 Clash/Mihomo 进程
  • 让 7890/7891/9090 监听消失
  • 让本地 API 失效

执行:

1
2
cx stop
cx ports

这时如果实现正常,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
2
cx ports
cx current

如果它们还正常,那说明只是:

  • 当前终端默认代理关了
  • 但 Clash 服务仍然活着

这正是 cx off 设计上该有的行为。


十、我现在的实际使用习惯

这次把整个工作流打通以后,我后面基本是这样用的。

开始工作前

1
2
3
cx start
cx on
cx test

工作过程中换节点

1
cx select

或者:

1
cx switch "日本Z02"

想确认自己当前到底走哪

1
2
cx current
cx country

暂时不想让当前终端默认走代理

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
2
3
4
5
6
7
8
cx start
cx on
cx test
cx current
cx select
cx country
cx off
cx stop

记住一句话就够了:

cx off 关的是当前终端默认代理,cx stop 关的才是 Clash 服务本身。