环境变量加载顺序:/etc/profile、~/.bashrc 与 systemd
发布时间:2026-01-14 21:57       
更新时间:2026-01-15 10:10
结论先说:变量"时而生效时而不生效",基本不是变量本身的问题,而是加载它的 shell 类型不同。分清登录 shell 与非登录 shell,这类问题就都有了答案。
## 两类 shell 的差别
通过 SSH 登录、su - 用户名 切换,属于登录 shell,它依次读取 /etc/profile,再读 ~/.bash_profile(没有则找 ~/.bash_login、~/.profile)。注意 su 不带减号只换身份不换环境,变量还是旧的,这是第一个常见的坑。/etc/profile 通常还会把 /etc/profile.d/ 下的 .sh 一并加载,全局变量适合放这里,例如:
```
echo 'export JAVA_HOME=/usr/lib/jvm/java-17' | sudo tee /etc/profile.d/java.sh
```
在已登录的终端里再开窗口、或直接执行 bash,属于交互式非登录 shell,只读 ~/.bashrc。很多发行版的 ~/.bash_profile 里写了 source ~/.bashrc,所以平时感觉不到差别,一旦改错文件,变量就"消失"了。遇到问题先确认自己改的到底是哪个文件。
## cron 和 systemd 为什么看不见变量
cron 执行任务时既不是登录也不是交互 shell,PATH 默认只有 /usr/bin:/bin 等几条,你自己 export 的变量一概不知。crontab 里命令找不到、脚本里 python 不是想要的版本,多半是这个原因。办法有两个:脚本里用绝对路径,或在 crontab 顶部写一行 PATH=/usr/local/bin:/usr/bin:/bin。举个具体例子,crontab 里写 30 2 * * * /usr/local/bin/backup.sh,脚本内部若调用 php、node 这类装在 /usr/local 下的程序,就必须写全路径,否则 cron 找不到。
systemd 服务同样不读上面那些文件,变量要在 unit 文件里用 Environment="KEY=value" 或 EnvironmentFile=/etc/app/env 声明,改完 systemctl daemon-reload 并重启服务,变量才会生效。
## 排查动作
怀疑变量没加载时,别猜,直接验证:
```
bash -l -c 'echo $PATH' # 模拟登录 shell
bash -c 'echo $PATH' # 模拟非登录 shell
ssh 主机 'echo $PATH' # 远程非交互命令走的正是这条路径
```
三条输出不同,就说明差异确实来自加载顺序。个别情况还要区分 env(只看环境变量)和 set(连 shell 内置变量一起看),确认变量到底进没进环境。再记住 source 与 sh 的区别:source 在当前 shell 生效,sh 开子进程跑完就丢,改完配置文件想立即生效,用 source 而不是 sh。预防上,通用变量放 /etc/profile.d/,个人变量放 ~/.bashrc,服务专用变量进 unit 文件,各归各位,出问题时一处一处查就行。