Linux 环境变量完全指南:从入门到理解底层原理
读完这篇文章,你将彻底搞懂环境变量是什么、怎么用,以及它背后的进程继承机制。适合零基础小白,也适合想补齐操作系统基础的开发者。
一、从一个问题说起:为什么敲 ls 系统就认识?
你有没有想过,当你在终端敲下 ls 并回车的那一刻,系统是怎么找到 ls 这个程序的?
它为什么不会说"命令未找到"?系统到底去哪里找的?
答案就藏在环境变量里——更具体地说,藏在 PATH 里。
二、什么是环境变量?
环境变量(Environment Variable)是操作系统级别的变量,由"变量名=变量值"的键值对组成,具有全局属性。
这句话拆开来看有三层意思:
- 它是变量:和编程语言里的变量一样,有一个名字,存一个值。
- 它是系统级的:它不属于某一个程序,而是操作系统维护的一份"全局配置表"。
- 它具有全局性:一个进程设置了环境变量,它的子进程也能"继承"到(这一点后面会展开讲,是理解环境变量的关键)。
你可以把环境变量理解为操作系统给所有程序准备的一份"全局备忘录"——程序启动时,系统会把这份备忘录塞到程序手里,程序需要什么信息自己去里面翻。
三、最重要的环境变量:PATH
3.1 PATH 是什么?
echo $PATH
输出可能长这样:
/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/maisui/.local/bin:/home/maisui/bin
这一串由冒号(:)分隔的路径列表,就是 命令搜索路径。
当你输入一个命令(比如 ls),Shell 会按照 PATH 中指定的路径,从左到右依次查找,找到第一个匹配的可执行文件就运行它。
3.2 如果我把 PATH 改了会怎样?
# 把 PATH 直接覆盖成自定义路径
PATH=/home/maisui/lesson_envir_var
这样一来,你的 Shell 只会在 /home/maisui/lesson_envir_var 这个目录下找命令。原来的 /usr/bin、/usr/local/bin 全都不认了——此时你连 ls 都用不了。
这是内存级的修改,重启 Shell 就会恢复。因为改的是当前进程的环境变量,没有动配置文件。后面我们会解释为什么。
3.3 正确姿势:追加而不是覆盖
# 在保留原 PATH 的基础上追加新路径
PATH=$PATH:/home/maisui/lesson_envir_var
这样原来的路径一个不少,还加上了你自己的目录。这也是各种开发环境配置(比如 Java 的 JAVA_HOME、Android SDK 等)的通用操作模式。
3.4 Windows 也有环境变量
Windows 同样有环境变量这个概念,PATH 的作用和 Linux 完全一致——指定命令/可执行文件的搜索路径。只不过 Windows 下路径分隔符是分号(;)而不是冒号(:),配置入口在"系统属性 → 高级 → 环境变量"。
四、你必须认识的常用环境变量
打好基础,我们先快速过一遍。这里列出的每一个变量都在日常开发中非常实用。
| 环境变量 | 含义 | 示例 |
|---|---|---|
PATH |
命令搜索路径 | /usr/local/bin:/usr/bin:... |
HOME |
当前用户的主目录(登录后的默认目录) | /home/whb |
SHELL |
当前用户默认使用的 Shell 解释器 | /bin/bash |
USER |
当前登录的用户名 | whb |
PWD |
当前所在的工作目录 | /home/whb/projects |
OLDPWD |
上一次所在的工作目录 | / |
HOSTNAME |
主机名 | bite-alicloud |
TERM |
终端类型(决定颜色、光标等行为) | xterm |
HISTSIZE |
内存中最多保存多少条历史命令 | 1000 |
下面挑几个值得展开的详细说说。
4.1 HOME —— 你的"家目录"
echo $HOME
# /home/whb
当你登录 Linux 时,系统默认把你放到这个目录。cd ~ 中的 ~ 展开后就是 $HOME 的值。很多程序(比如 git、ssh、npm)都会去 $HOME 下找配置文件(.gitconfig、.ssh/config、.npmrc 等),所以这个变量比你想象的更重要。
4.2 SHELL —— 你在用哪个 Shell?
echo $SHELL
# /bin/bash
它记录的是当前用户默认使用的 Shell 解释器。Linux 下有多种 Shell(bash、zsh、fish、dash 等),这个变量告诉你现在用的是哪个。注意:它显示的是默认 Shell,并不代表你当前一定在用这个(你可以从 bash 里启动 zsh)。
4.3 HISTSIZE —— 你的命令历史
echo $HISTSIZE
# 1000
HISTSIZE 控制 bash 在内存中最多保存多少条执行过的命令。你敲 history 看到的记录条数就受这个值限制。
注意区分:
HISTSIZE控制内存中的历史条数,而HISTFILESIZE控制磁盘上.bash_history文件保存的条数。你注销后,内存中的历史会写入磁盘文件,下次登录再读回来。
4.4 OLDPWD —— 一键回到上一个目录
echo $OLDPWD
# /
cd - 命令就是读取这个变量,跳回上一次所在的工作目录。这个变量由 Shell 自动维护。
4.5 SSH 相关变量(远程登录专属)
如果你是通过 SSH 连接到服务器的,你会看到这些额外变量:
SSH_CLIENT=113.132.215.158 17705 22
SSH_TTY=/dev/pts/0
| 变量 | 含义 |
|---|---|
SSH_CLIENT |
格式:客户端公网IP 客户端端口 服务器SSH端口。这里是你的电脑从 17705 端口连接服务器的 22 端口 |
SSH_TTY |
SSH 会话绑定的虚拟伪终端设备,pts 就是远程登录用的虚拟终端。每个 SSH 连接分配一个 pts 设备 |
这两个变量仅在 SSH 远程登录时自动设置。如果你直接在服务器本地插显示器键盘登录,是不会有的。
4.6 XDG_SESSION_ID —— 登录会话编号
XDG_SESSION_ID=36981
由 systemd 生成,用来标记当前这次登录会话。不管是 SSH 远程登录还是图形桌面登录,都会分配一个唯一 ID。系统管理员排查问题时经常用到。
五、操作环境变量的常用命令
5.1 查看环境变量
# 方法一:查看单个变量
echo $变量名
echo $PATH
# 方法二:查看所有环境变量
env
# 或者
printenv
# 方法三:查看所有变量(包括环境变量 + Shell 本地变量)
set
三者的区别很重要:
| 命令 | 显示范围 |
|---|---|
env / printenv |
只显示环境变量 |
set |
显示环境变量 + Shell 本地变量 |
关键区别:环境变量可以被子进程继承,Shell 本地变量不能被继承。
set能看到本地变量,env看不到——因为本地变量只在当前 Shell 内部有效。
5.2 创建和修改变量
# 创建本地变量(只在当前 Shell 有效,子进程拿不到)
TEST_ENV=12342
# 把已有的本地变量"升级"为环境变量(子进程可以继承了)
export TEST_ENV
# 一步到位:直接创建环境变量
export aaa=12345
5.3 删除变量
unset aaa
unset TEST_ENV
5.4 验证变量是否存在
echo $TEST_ENV # 有值就说明存在
echo $aaa # 空行说明不存在或为空
六、底层原理:环境变量到底是怎么传递的?
这是本文的重头戏。如果你能理解这一节,你对环境变量的认知就超越了 90% 的开发者。
6.1 环境变量存在哪里?
环境变量并不是存在某个"全局数据库"或"注册表"里——它本质上是存在进程的内存空间中的一张表。
每个进程启动时,操作系统会把一份环境变量表"塞"给它。这张表和命令行参数表一样,都是内存级的、临时的。
6.2 子进程会继承父进程的环境变量
核心规则:子进程可以拿到父进程的环境变量。
为什么?因为你在终端里运行的所有命令,都是当前 Shell 进程(比如 bash)的子进程。bash 通过 fork() 创建子进程时,子进程会继承父进程(bash)的环境变量表。
用一张图来理解:
系统配置文件(磁盘)
↓ 读取
bash 进程(内存中,持有环境变量表)
↓ fork() + exec()
你的程序(作为 bash 的子进程,继承了环境变量表)
6.3 为什么重启 Shell 后 PATH 修改会丢失?
现在我们来解释前面留下的那个问题:
PATH=$PATH:/home/maisui/lesson_envir_var # 改了 PATH
# 关闭终端,重开...
echo $PATH # PATH 又恢复了!
原因很简单:你用 export 修改的是当前 bash 进程的内存中的环境变量,你并没有写磁盘上的配置文件。bash 进程一死,这些修改就跟着消失了。下次启动新 bash 时,它重新从配置文件里读,拿到的当然是原来的值。
修改环境变量(
export)= 改内存 → 临时生效,重启丢失修改配置文件(
.bashrc等)= 改磁盘 → 永久生效
6.4 用代码验证继承关系
光说不练假把式,下面用 C 代码来验证。
验证一:遍历环境变量表(通过 main 的第三个参数)
#include <stdio.h>
int main(int argc, char *argv[], char *env[]) {
(void)argc;
(void)argv;
int i = 0;
for (; env[i]; i++) {
printf("env[%d]: %s\n", i, env[i]);
}
printf("我是一个命令,自己实现的!\n");
return 0;
}
main 函数其实可以有三个参数,第三个 char *env[] 就是父进程传给它的环境变量表。编译运行后你会看到所有环境变量都被打印了出来。
验证二:使用全局变量 environ
#include <stdio.h>
#include <unistd.h>
int main() {
extern char **environ; // 系统提供的全局变量
int i = 0;
for (; environ[i]; i++) {
printf("environ[%d] -> %s\n", i, environ[i]);
}
return 0;
}
char **environ 是系统默认给定的全局变量,和 env[] 指向的是同一张表。
验证三:用 getenv() 获取单个环境变量
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() {
char *whoami = getenv("USER"); // 获取 USER 这个环境变量的值
if (whoami == NULL) {
printf("无法执行我,因为我不认识你\n");
} else if (strcmp(whoami, "root") == 0) {
printf("我不准 root 执行\n");
} else if (strcmp(whoami, "whb") == 0) {
printf("你是合法用户,执行吧\n");
}
return 0;
}
getenv("USER") 会去环境变量表里查找 USER 对应的值。这个函数在实际开发中非常实用——比如你可以根据环境变量切换开发/生产配置。
6.5 自己造一个"环境变量表"传给子进程
我们甚至可以不依赖 bash,手动构造环境变量表来喂给子进程:
#include <stdio.h>
#include <unistd.h>
char *argv[100];
char *env[100];
int main() {
argv[0] = (char*)"ls";
argv[1] = (char*)"-a";
env[0] = (char*)"PATH=AAAA"; // 自定义环境变量
pid_t id = fork();
if (id == 0) {
// 子进程:打印自己收到的参数和环境变量
int i = 0;
for (; argv[i]; i++)
printf("argv: %s\n", argv[i]);
for (i = 0; env[i]; i++)
printf("env : %s\n", env[i]);
} else {
// 父进程:不退出,方便观察
sleep(10000);
}
return 0;
}
这个例子说明:环境变量表的传递,本质上就是父进程把自己的那张表复制一份给子进程。不是什么神秘的"全局数据库",就是朴实无华的内存拷贝。
七、本地变量 vs 环境变量:一张表说清楚
| 特性 | 本地变量 | 环境变量 |
|---|---|---|
| 创建方式 | VAR=value |
export VAR=value |
| 查看方式 | set 可以看到 |
env / printenv 可以看到 |
| 作用范围 | 仅在当前 Shell 内部 | 当前 Shell + 所有子进程 |
| 子进程能继承吗? | ❌ 不能 | ✅ 能 |
| 典型用途 | 脚本内部临时变量 | 全局配置(PATH、HOME 等) |
一句话总结:本地变量是 Shell 的"私有财产",环境变量是"传给下一代的遗产"。export 就是办遗产公证的那个动作。
八、如何让环境变量永久生效?(配置文件)
既然内存级的修改重启就丢了,那怎么让修改永久保存呢?
答案:写到配置文件中。
8.1 配置文件在哪里?
每个用户的家目录下都有几个特殊的文件:
| 文件 | 作用 |
|---|---|
~/.bash_profile |
登录 Shell 时执行(启动时读一次) |
~/.bashrc |
每个交互式 Shell 启动时执行(每开一个终端就读一次) |
~/.profile |
通用 profile,部分发行版使用 |
/etc/profile |
全局配置,对所有用户生效(需要 root 权限) |
8.2 如何操作?
# 编辑 .bash_profile(或 .bashrc)
vim ~/.bash_profile
# 在里面加上:
export maisui=shuaige
export PATH=$PATH:/home/whb/my_bin
# 保存退出后,让配置立刻生效:
source ~/.bash_profile
这样设置后,每次重新登录(或重新打开终端),这些变量就会被自动加载。
小建议:个人配置改
~/.bashrc,系统级全局配置才改/etc/profile。给每个改动加上注释,半年后你会感谢现在的自己。
九、环境变量的实际应用场景
知道了原理,来看几个你在日常开发中一定会遇到的场景:
- 多版本管理(Java/Python/Node.js):通过切换
JAVA_HOME、PYTHON_HOME等环境变量来切换版本,而不是反复安装卸载。 - 开发/生产环境区分:程序启动时读
ENV=production或ENV=development,自动切换数据库连接、日志级别等。 - 敏感信息管理:数据库密码、API Key 等写在环境变量里,不硬编码到代码中(12-Factor App 的核心原则之一)。
- 工具链配置:
ANDROID_HOME、GOPATH、MAVEN_HOME等各种开发工具都依赖环境变量找到自己的家目录。
十、总结
回顾一下本文的核心脉络:
配置文件(磁盘)→ bash 进程读取(内存)→ fork 子进程(继承)→ 你的程序拿到环境变量
- 环境变量是操作系统维护的全局键值对,保存在进程的内存空间中。
PATH是最重要的环境变量,决定了你的命令去哪里找可执行文件。env看环境变量,set看所有变量(含本地变量)。export创建环境变量,unset删除变量,修改是内存级的。- 子进程会继承父进程的环境变量——这是环境变量"全局性"的根本原因。
- 要永久生效,需要修改配置文件(
~/.bashrc等),而不仅仅是export。 - 在 C 程序中,可以通过
char *env[]、extern char **environ、getenv()三种方式访问环境变量。
搞懂了环境变量,你就理解了操作系统进程间信息传递的一个基本范式。下次配开发环境时,你就不只是"照教程改几行配置"了——你很清楚每一行在做什么、为什么这样做、不这样做会怎样。
本文涵盖了从基础操作到底层原理的完整知识链,适合作为环境变量的系统性参考。
如果你觉得有帮助,欢迎点赞收藏,也欢迎在评论区交流讨论!
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐



所有评论(0)