← 返回首页

一次 Linux PATH 踩坑记:为什么新开的终端找不到我的命令

服务器运维 亲身经历 公开
内容简要
坑点
注意事项

一次 Linux 踩坑记:从 PATH 到 file:./,我踩了两次同一个坑

一、起因:想让脚本直接敲名字就能跑

服务器上部署了几个 Spring Boot 的启停脚本,都放在 /data/springboot。我不想每次都敲全路径 /data/springboot/start.sh,就想跟同事一样,直接输入 start.sh 就能跑。

上网搜了一下,清一色说 export PATH=$PATH:/data/springboot。我照做,果然当前窗口能用。收工,挺简单。

二、第一个坑:第二天命令全"失踪"了

第二天早上,我打开电脑,SSH 连上服务器,新开了一个终端标签,手一敲 start.sh,系统冷冰冰地回了一句:

-bash: start.sh: command not found

我又 echo $PATH,确实没有 /data/springboot。奇怪,明明昨天加过。再 source ~/.bashrc,又好了。我才反应过来:昨天那个 export 只是临时的,关掉就没了。

三、第二个坑:写在 .bash_profile 里,新标签还是不行

我学聪明了,这次写进"永久"文件。我挑了 ~/.bash_profile,把那行 export 加进去。

注销重登,start.sh 能用。我心想这下稳了。

结果一开新标签,又 command not found。

我愣了半天,才搞懂一件事:登录和开 shell 是两回事。

  • 我早上 ssh 连服务器,那是"登录",会读 .bash_profile。
  • 可我后来点开的那些新标签,只是又开了个交互 shell,并没有重新登录,所以根本不读 .bash_profile,自然读不到我的 PATH。

一句话:登录只发生一次,shell 能开无数次。​ 所以 PATH 这种每次都要的东西,得放每次都读的 ~/.bashrc。

四、第三个坑:PATH 越来越长

中间还有一段插曲。我为了让新标签能用,在 .bashrc 里直接追了句 export PATH=$PATH:/data/springboot。

结果每开一个标签、每 source 一次,PATH 里就多一份 /data/springboot。用 echo $PATH 一看,尾巴长得离谱,一坨重复的。这才学会加个判断:

if [[ ":$PATH:" != *":/data/springboot:"* ]]; then
    export PATH=$PATH:/data/springboot
fi

五、第四个坑:PATH 修好了,应用却起不来了

PATH 搞定后,start.sh 在哪都能敲了。我美滋滋地在自己家目录 ~ 执行了一下 start.sh,想验证"全局可用"。

结果 Spring Boot 报了一长串错,最底下藏着这么一句:

onnx-bge-zh/tokenizer.json (No such file or directory)

上面还有一大串 UnsatisfiedDependencyException,从 pitfallChatService 一路套到 PgVectorStoreAutoConfiguration,再到 TransformersEmbeddingModelAutoConfiguration。

我一开始以为是代码改坏了,慌了半天。后来仔细一对,发现在 /data/springboot 目录里启动是正常的,一换个目录就炸。

我又一次陷入了"明明昨天还好好的"的困惑。

六、真相:file:./ 是个定时炸弹

打开配置一看,根因就在眼前:

model-uri: file:./onnx-bge-zh/model.onnx

file:./ 里的 ./,是相对于你执行 java -jar 时的"当前工作目录",不是相对于 jar 所在目录。

我之前在某台机器、某个目录里部署时,顺手把 onnx-bge-zh/ 模型文件夹解压到了那个目录。所以:

我在哪启动那个目录下有没有 onnx-bge-zh/结果
之前部署过的那个目录✅ 有(我当初解压到那了)正常
/data/springboot❌ 没有炸
~ 家目录❌ 没有炸

而且那一大串依赖注入报错,其实根因只有一个文件找不到:tokenizer.json 找不到 → 嵌入模型建不出来 → 向量库建不出来 → 依赖它的 Service 全建不出来。最底下的零件坏了,上面的 Bean 全会报 UnsatisfiedDependencyException。

所以这件事跟代码改动完全无关,纯粹是"启动目录里有没有这个模型文件夹"的问题。

七、解法:把"工作目录"这个变量消灭掉

我用了两层保险。

第一层,启动脚本里先把目录钉死:

#!/bin/bash
# /data/springboot/start.sh
cd "$(dirname "$0")" || exit 1   # 无论谁、从哪调用,都先回到脚本所在目录
export PITLOG_MODEL_DIR="$(pwd)/onnx-bge-zh"
exec java -jar pitlog.jar "$@"

cd "$(dirname "$0")" 是精髓——它让"当前目录"永远等于"脚本所在目录",跟我从哪儿敲命令无关。

第二层,配置改成支持环境变量,带兜底默认值:

model-uri: file:${PITLOG_MODEL_DIR:./onnx-bge-zh}/model.onnx
tokenizer-uri: file:${PITLOG_MODEL_DIR:./onnx-bge-zh}/tokenizer.json

:./onnx-bge-zh 这个默认值不能删——它是忘了设环境变量时的兜底,也是别人 clone 代码后本地能直接跑的保障。

后来上了 systemd 服务,我直接把变量写进 service 文件:

[Service]
Environment=PITLOG_MODEL_DIR=/data/springboot/onnx-bge-zh
ExecStart=/usr/bin/java -jar /data/springboot/pitlog.jar

因为前面刚踩过坑:systemd 和 cron 默认读不到​ .bashrc 里的 export,写在脚本里的环境变量在那些场景下是白写的。

八、踩完这一圈,我记住了三句话

  1. 登录是验身份,交互 shell 是能打字;登录只一次,shell 能开很多次。PATH 放 ~/.bashrc。
  2. file:./ 是定时炸弹——它让"能不能启动"取决于你在哪个目录敲的命令。改成 file:${环境变量:默认兜底} 是根治。
  3. cron、systemd、后台脚本读不到你的 shell 配置;环境变量要么写进 systemd 的 Environment=,要么写进 /etc/profile.d/。

回头看,我其实是同一个坑踩了两次:第一次是以为 export 写了就永久生效,第二次是以为 file:./ 能找到文件。本质上都是——把一个"运行时状态"当成了"固定事实"。PATH 取决于你 source 了没,文件路径取决于你在哪个目录。想稳,就得把它们钉死。

本文内容仅代表作者个人观点、经历或转述,不代表任何官方立场,仅供参考,不构成任何专业建议。据此操作产生的风险由读者自行承担。
发布于 2026-11-04 08:37:47 ID: 41