这篇是上一篇《Linux 上把 Clash 用顺:开启代理、切换节点、查询出口、脚本与日常工作流》的续篇。

上一篇我已经把下面这些基础能力打通了:

  • 在 Linux 终端里开启/关闭当前 shell 代理
  • 通过 Clash API 查看当前节点、列出节点、切换节点
  • 用脚本把日常命令统一成 cx
  • cx testcx country 验证代理是否真的可用

在实际使用时,我遇到的一个常见问题是:它很容易造成误解:

我明明已经执行了 cx off,为什么 cx ports 还能看到 7890/7891/9090 在监听?
为什么 cx current 还能返回当前节点?
为什么有时候 cx test 甚至还能通?

如果第一次做终端代理工作流,这个现象通常会让人误以为:

  • cx off 没生效
  • 脚本写错了
  • Clash 没有真正关闭
  • 代理环境变量没有被清掉

其实都不是。

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

  1. cx oncx offcx stop 到底分别在控制什么
  2. 为什么关掉“当前终端代理”之后,Clash 仍然会继续监听
  3. 为什么 cx currentcx 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_proxyhttps_proxy 有没有被 unset,根本不是一回事。

所以:

cx off 不会让 cx ports 变空。

如果你想让 cx ports 变空,那你要执行的是:

1
cx stop

四、为什么 cx currentcx 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 oncx offcx 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 服务本身。