这篇主要记一个很容易误判的问题:mamba activate 之后,topfind 这类系统命令突然“消失”了。
一开始很容易怀疑是系统没装这些命令,但只要对比激活前后,就会发现真正的问题其实是 PATH 被改坏了。

1、现象

激活前,top 是正常的:

$ top -hv
  procps-ng 3.3.15

激活环境后却不行:

$ mamba activate xj_py39
$ top -hv
bash: top: 未找到命令...

继续检查 PATH

echo "$PATH"
mamba activate xj_py39
echo "$PATH"

type -a top
which top

# 看看激活的时候会不会有什么操作
find "$CONDA_PREFIX/etc/conda/activate.d" -maxdepth 1 -type f
# 激活前(已脱敏)
/custom/tool/bin:/usr/bin:/opt/meme/bin:/usr/bin:/usr/condabin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/opt/tools:/root/bin

# 激活后(已脱敏)
/root/miniforge3/envs/xj_py39/bin:/custom/tool/bin:/opt/meme/bin:/usr/condabin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/opt/tools:/root/bin

关键区别非常明显:

  • 激活前有 /usr/bin
  • 激活后 /usr/bin 消失了

这就能解释为什么 topfind 都找不到了,因为这些系统命令本来就在 /usr/bin 里。

2、真正原因

这类问题的重点不是“命令没安装”,而是:

mamba activate 之后,PATH 没有正确保留系统路径。

正常情况下,激活环境应该只是把环境自己的 bin 放到最前面,例如:

/root/miniforge3/envs/xj_py39/bin:/usr/local/bin:/usr/bin:/bin:...

而不是把 /usr/bin/bin 这类系统路径直接弄丢。

所以如果你遇到的是这种情况:

  • 激活前 top 能用
  • 激活后 top 不能用
  • 激活后 PATH 里少了 /usr/bin

那就已经可以基本确定:问题在 PATH,而不是 top 本身。

常见来源一般有三类:

  • ~/.bashrc~/.bash_profile 里有不当的 PATH 覆盖;
  • 环境中的 etc/conda/activate.d/*.sh 改写了 PATH
  • 某些工具自己的初始化脚本和 conda 激活逻辑互相冲突。

3、临时修复方式

你的这个问题,直接把系统路径补回去就恢复了:

export PATH=$PATH:/usr/bin:/usr/sbin

然后再测:

$ top -hv
  procps-ng 3.3.15

这个命令之所以有效,是因为它做的是“追加”,不是“前置”。

这里顺带记一个很重要的区别:

# 不推荐:把系统路径放到最前面
export PATH=/usr/bin:$PATH

# 更合适:把系统路径追加到后面
export PATH=$PATH:/usr/bin:/usr/sbin

原因很简单:

  • 前置 /usr/bin 可能把 conda 环境里的 pythonpip 顶掉;
  • 追加 /usr/bin 则能保留环境优先级,同时让系统命令重新可用。

4、怎么定位是谁改坏了 PATH

如果只是临时用,前面的 export 已经够了。
如果想彻底定位原因,建议按这个顺序查:

echo "$PATH"
mamba activate xj_py39
echo "$PATH"

type -a top
which top

如果激活后 /usr/bin 消失,就继续查:

ls "$CONDA_PREFIX/etc/conda/activate.d"

再检查:

grep -n 'PATH=' ~/.bashrc ~/.bash_profile
/root/.bashrc:25:export PATH=/root/meme/bin/
/root/.bashrc:199:        export PATH="/usr/bin:$PATH"
/root/.bashrc:218:export PATH=/你的路径/SAW/bin:$PATH
/root/.bash_profile:10:PATH=$PATH:$HOME/bin

/你的路径/SAW/bin 这种字面量占位符属于脏配置,建议后续单独清理;但它不是这次主线问题。

重点看有没有这种危险写法:

export PATH=/某个路径

或者这种会覆盖而不是追加的写法:

PATH=/某个路径:$PATH

只要写得不小心,就可能在激活 conda 环境时把系统路径链条弄断。

5、继续往下查:真正的罪魁祸首是谁

继续排查后,问题其实已经可以实锤了。

先看最关键的几条输出:

$ echo "$CONDA_PREFIX"
/usr

$ conda info
active environment : base
active env location : /usr
base environment : /usr  (read only)
conda version : 4.10.3
python version : 3.6.8

再看 conda 的来源:

$ type -a conda
conda 是函数
conda 是 /usr/bin/conda
conda 是 /usr/condabin/conda

这几条信息已经足够说明:

  • 当前 shell 里的 (base) 不是 ~/miniforge3
  • 它是系统自带的那套 conda;
  • 它的 base 前缀就是 /usr

这就是这次问题的真正元凶。

5.1 为什么这会导致 /usr/bin 被删掉

conda/mamba 在切换环境时,会先把“旧环境前缀”对应的 binPATH 中移除,再把“新环境前缀”的 bin 插进去。

你当前的旧环境前缀是:

$ echo "$CONDA_PREFIX"
/usr

那么它要移除的旧环境 bin 就是:

/usr/bin

而你激活的新环境却是:

/root/miniforge3/envs/xj_py39

也就是说,你当前实际干的事情并不是“从 Miniforge base 切到 Miniforge 子环境”,而是:

从系统自带 conda 的 /usr base,切到 ~/miniforge3 里的另一个环境。

这样一来,/usr/bin 被当成“旧环境路径”删掉,就完全解释得通了。

然后使用mamba activate xj_py39后就会把$CONDA_PREFIX/bin加到PATH前面去。

$echo "$CONDA_PREFIX"
/root/miniforge3/envs/xj_py39

5.2 为什么前面那些怀疑都可以排除了

第一,xj_py39 环境本身几乎没有可疑激活脚本:

$ tree ~/miniforge3/envs/xj_py39/etc
~/miniforge3/envs/xj_py39/etc
├── conda
│   └── test-files
└── request-key.conf

也就是说,xj_py39/etc/conda/activate.d/ 并没有明显在改 PATH

第二,~/.bashrc 里虽然有不少 PATH 相关内容,但从这次贴出来的片段看,基本都是“追加”,并没有直接出现“把 /usr/bin 删除掉”的写法。

这就说明:

  • xj_py39 环境本身不是元凶;
  • 你贴出来的这段 ~/.bashrc 不是直接元凶;
  • 真正的问题是 系统 conda 的 (base) 和 Miniforge 环境被混用了

不过补一句,~/.bashrc 虽然不是这次“删掉 /usr/bin”的直接元凶,但它本身也已经有明显的历史污染了。例如:

export PATH="/usr/bin:$PATH"
export PATH=/你的路径/SAW/bin:$PATH

前者会把系统路径前置,容易顶掉环境里的 python/pip;后者更像是某个教程、模板或自动生成内容里的占位符没有替换干净,属于应该尽快清掉的脏配置。

也就是说,这次问题有两层:

  • 直接触发器:系统 /usr base 和 ~/miniforge3 环境混用;
  • 背景噪音:~/.bashrc 本身也不够干净,里面混有可疑 PATH 写法。

5.3 一句话总结这次根因

这次不是某个命令坏了,而是:

系统自带 conda 的 /usr base 和 ~/miniforge3 里的环境混用,导致环境切换时把 /usr/bin 当成旧环境路径删掉了。

6、真正该怎么修

临时修复当然还是:

export PATH=$PATH:/usr/bin:/usr/sbin

但如果想从根上修,重点不是反复补 PATH,而是只保留一套 conda 初始化链路。

这次手动验证其实已经说明问题了:

source /root/miniforge3/etc/profile.d/conda.sh
conda activate base
echo "$CONDA_PREFIX"

输出变成:

/root/miniforge3

随后再执行:

mamba activate xj_py39
command -v top

结果恢复正常:

/usr/bin/top

这说明:

  • xj_py39 环境本身没问题;
  • Miniforge 这套初始化本身也没问题;
  • 真正的问题就是 shell 启动后先进了系统 /usr 这套 conda base。

接下来可以分成两种启动状态来看。

6.1 分支 A:新 shell 一上来就落在系统 /usr(base)

这是最容易触发问题的情况。表现通常是:

echo "$CONDA_PREFIX"
/usr

如果是这种状态,再去激活 ~/miniforge3/envs/xj_py39,就可能把 /usr/bin 当成旧前缀删掉。

这种情况下,修法是:

  1. 明确以后只用 ~/miniforge3 这套;
  2. 让 shell 初始化 ~/miniforge3/etc/profile.d/conda.sh
  3. 不要在同一个 shell 会话里混用系统 conda 和 Miniforge mamba。

用户级最直接的修法是,在 ~/.bashrc 末尾保留:

if [ -f "/root/miniforge3/etc/profile.d/conda.sh" ]; then
    . "/root/miniforge3/etc/profile.d/conda.sh"
fi

如果系统脚本仍然会先把 /usr 的 base 激活起来,还可以手动执行:

source /root/miniforge3/etc/profile.d/conda.sh
conda activate base

确认当前已经切回:

echo "$CONDA_PREFIX"
/root/miniforge3

6.2 分支 B:新 shell 启动时不激活任何 base,只保留普通 PATH

这次新的 shell 窗口其实就是这种情况:

echo "$CONDA_PREFIX"

echo "$PATH"
/root/meme/bin:/usr/bin:...

此时没有任何旧 conda 前缀需要被移除,所以直接执行:

mamba activate xj_py39

往往反而是最干净、最稳定的做法。

也就是说,如果你的新 shell 默认已经满足下面三点:

  • CONDA_PREFIX 为空;
  • PATH 里保留 /usr/bin
  • 直接 mamba activate xj_py39top 正常;

那就没必要再强行登录就 conda activate base。直接按需激活目标环境即可。

6.3 哪种更推荐

如果能选,我更推荐分支 B:

  • 默认不自动激活任何 base;
  • 需要时直接 mamba activate xj_py39
  • 避免系统 /usr base 和 Miniforge base 混在一起。

分支 A 也能用,但前提是你要确保进入的是 Miniforge 的 base,而不是系统 /usr 的 base。

7、顺手记两条环境管理经验

7.1 编译型包优先用 mamba

cooltoolsopenTSNEpytables 这类包,如果 conda-forge 里有,优先:

mamba install -c conda-forge cooltools opentsne pytables

比直接 pip install 更省心,因为它通常能一起处理好 gccfftw 这类底层依赖。

7.2 安装前先看会改哪些包

mamba install -c conda-forge 包名 --dry-run

如果你不想让已有依赖乱跳版本,可以试:

mamba install -c conda-forge 包名 --freeze-installed

这两条命令在维护老环境时很有用。

8、结论

这次踩坑最关键的结论其实就一句:

mamba activate 后系统命令消失,不一定是命令没装,更可能是 PATH 在激活过程中被错误改写了。

判断方法也很直接:

  1. 先看激活前命令能不能用;
  2. 再看激活后 PATH 里是否还保留 /usr/bin
  3. 如果少了,就先手动补回:
export PATH=$PATH:/usr/bin:/usr/sbin

如果手动 source /root/miniforge3/etc/profile.d/conda.sh && conda activate base 之后一切恢复正常,就说明问题确实出在 conda 初始化链路,而不是 topfind 这些命令本身。

再进一步说,这次更像是:

当前 (base) 其实是系统自带 conda 的 /usr,而不是 Miniforge 的 base;因此切换到 xj_py39 时,/usr/bin 被当成旧环境路径移除了。

所以真正该修的,不是 top,而是 conda/mamba 的初始化链路混用问题

最后可以记成一个简单分支:

  • 如果新 shell 一上来就是 /usr(base),先切回 Miniforge,再激活目标环境;
  • 如果新 shell 默认没有激活任何 base,而且 PATH 正常,那就直接 mamba activate xj_py39,不用多做一步。

9、再补一层原理:为什么“空环境”反而更安全

这次现象还有一层很值得记一下,不然很容易只记住结论,不知道背后的逻辑。

9.1 conda 激活环境,本质上分两种情况

第一种是“0 到 1”:

  • 当前 shell 没有激活任何 conda 环境;
  • CONDA_PREFIX 为空;
  • 这时执行 mamba activate xj_py39,conda 只需要把 xj_py39/bin 加进 PATH

第二种是“1 到 2”:

  • 当前 shell 已经激活了一个 conda 环境;
  • 这时再激活另一个环境,conda 会先把旧环境对应的 binPATH 里移掉,再把新环境的 bin 加进去。

这就像:

  • xj_r -> dy_r
  • base -> xj_py39

本质上都属于“从一个已激活环境切到另一个已激活环境”。

9.2 这次为什么会把 /usr/bin 删掉

关键不在于 conda 的逻辑错了,而在于它识别到的“旧环境”不是你以为的那个。

这次旧环境其实是系统 conda 的:

prefix = /usr

注意这里要分清:

  • prefix 是环境根目录;
  • bin 是这个环境里放可执行文件的目录。

所以:

prefix = /usr
bin    = /usr/bin

当 conda 认为当前旧环境是 /usr 时,切换到 xj_py39 的过程中,就会把:

/usr/bin

当成旧环境的 bin 移除。

topfindwhich 这类系统命令,本来就依赖 /usr/bin,所以它们就一起“消失”了。

9.3 为什么“空环境”时反而没事

当新的 shell 窗口里:

echo "$CONDA_PREFIX"

输出为空时,含义不是“有个隐藏的 base”,而是:

当前根本没有激活任何 conda 环境。

这时候再执行:

mamba activate xj_py39

conda 处理的是“0 到 1”,它只会把:

/root/miniforge3/envs/xj_py39/bin

加进来,而不会先删一个旧环境的 bin

因此 /usr/bin 会继续保留,top 也就正常。

9.4 为什么 conda activate base 不一定会暴露这个问题

这也是这次最容易让人困惑的一点。

如果你已经处在系统 conda 的 (base),再执行一次:

conda activate base

很多时候不会看到 top 消失。原因不是问题不存在,而是:

  • 旧环境本来就是 /usr
  • 新环境还是 /usr
  • conda 就算重算一遍,也会把 /usr/bin 再加回来

所以这种情况下,问题会被“覆盖掉”。

但从:

/usr base -> /root/miniforge3/envs/xj_py39

切过去时就不一样了:

  • 它会删掉 /usr/bin
  • 加回来的是 xj_py39/bin
  • 而不是 /usr/bin

于是问题就暴露出来了。

9.5 这次可以记住的一句话

这次真正该记住的不是“top 为什么坏了”,而是:

conda 环境切换时,会先移除旧环境的 bin;如果旧环境被错误识别成系统 /usr,那 /usr/bin 也会一起被移掉。