长久以来,被Linux下的环境变量搞得有些混乱,linux系统本身有环境变量,另外,gcc make buildroot shell的操作过程中,也涉及到了很多环境变量,比如CFLAGS MAKEFLAGS 等等,这些环境变量说的是同一个东西吗,还是不同工具维护着各自的一套环境变量?
环境变量机制
环境变量是操作系统提供的统一机制,所有程序(shell、gcc、make、buildroot)读取的是同一套环境变量命名空间;但不是所有环境变量对所有程序都生效,很多变量只是某些工具约定的专用变量,操作系统本身并不认识它们。
区分两个概念:
- 环境变量机制:内核 / 操作系统的能力,进程启动时会继承父进程的环境变量(key=value 字符串列表)。所有进程共用这套机制。
- 变量含义 :
CFLAGS、MAKEFLAGS这类名字,不是 Linux 系统自带的系统变量 ,只是 gcc/make 社区约定好、工具自己会去读取的变量。Linux 内核本身完全不知道CFLAGS是什么意思。
1. Linux 的环境变量基础(系统层面)
当你在 shell(bash/zsh)里工作:
shell 是一个进程,它拥有一份环境变量列表。
shell 启动任何子进程(
ls、gcc、make、buildroot 脚本),默认会把自己的环境变量复制一份传给子进程。子进程修改环境变量,只会影响自己和它的子进程,不会反向修改父 shell。这是最容易踩坑的点。
在shell里设置
export CFLAGS="-O2"
这时当前shell的环境里有 CFLAGS
后续执行 gcc / make,子进程拿到这个变量,读取使用
如果不加
export,只是 shell 的局部变量,不会传给子进程,工具就读不到。系统自带变量:
PATH、HOME、USER、LD_LIBRARY_PATH、SHELL等,这些很多程序都会读取,属于通用环境变量。
2. 工具专用变量:CFLAGS、MAKEFLAGS 是什么?
CFLAGS
不是 Linux 系统变量,是编译工具链(gcc、clang)约定的变量
make 在执行编译规则(默认内置
%.c:%.o隐式规则)时,会自动读取CFLAGS,加到 gcc 的命令行参数。如果你直接手动调用 gcc ,
gcc本身不会自动读取 CFLAGS !
gcc test.c # 不会读 CFLAGS make # make 在隐式规则里,会读取 CFLAGS 传给 gcc关键点:
CFLAGS是 make 的约定 ,不是 gcc 程序内置读取的环境变量。类似还有CXXFLAGS(c++)、LDFLAGS(链接参数)。MAKEFLAGS
- 是 make 工具专属环境变量
- make 启动时,会读取
MAKEFLAGS,里面存放 make 的命令行选项(比如-j并行编译)- 当 make 递归调用子 make(
make -C subdir),会自动把参数放进MAKEFLAGS传给子 make,实现并行参数继承。- 别的程序(gcc、shell)完全不认识
MAKEFLAGS。总结:
- 存储载体:操作系统统一的环境变量
- 解释者:对应工具自己的代码去 getenv ("变量名"),读到之后按工具自己的规则解析。
- Linux 内核只是负责传递字符串,不理解变量语义。
3. Buildroot 的环境变量
Buildroot 本质是一堆 shell 脚本 + Makefile。
- buildroot 运行时,它会修改当前 make 进程的环境变量 (追加 / 覆盖
PATH、CFLAGS、CXXFLAGS、PKG_CONFIG_PATH等)- 这些变量通过进程继承,传递给它调用的 gcc、pkg-config 等工具。
- buildroot 有自己的一套逻辑:比如区分主机工具环境 (host 编译)和目标板工具链环境 (target 交叉编译),切换不同的
PATH和CFLAGS。⚠️ 坑点:buildroot 在
make执行过程中设置的环境变量,只存在于 buildroot 这个 make 进程以及它的子进程。buildroot 执行结束退出后,你回到交互式 shell,这些变量就消失了,不会留在你的终端里。
4. 一张对比表
变量 是谁定义 / 读取 Linux 系统是否认识 PATH, HOME, USER shell / 系统,大量程序读取 系统基础环境变量 CFLAGS, LDFLAGS, CXXFLAGS make 隐式规则约定,传给编译器 系统不认识,只是字符串 MAKEFLAGS make 程序专用 系统不认识 BUILDROOT_* 各种变量 buildroot 脚本自己使用 系统不认识 5. 常见误区澄清
误区 1:gcc 自己读取 CFLAGS
❌ 错。
gcc不会读取环境变量CFLAGS。是 make 在调用 gcc 的时候,把 $CFLAGS 拼到命令行。你写 Makefile:
test.o: test.c gcc $(CFLAGS) -c test.cmake 展开变量,执行:
gcc -O2 -c test.c。gcc 拿到的只是命令行参数,它根本不知道有个叫CFLAGS的环境变量。误区 2:各个工具维护独立的多套环境变量
❌ 不是多套存储。**所有子进程继承来自父进程的同一份环境变量列表。**区别只是:A 工具会读名字叫 A 的变量,B 工具只读 B 变量,其他变量它直接忽略。
误区 3:在脚本里 export 变量,退出 shell 还保留
❌ 进程隔离。子进程环境修改不会回写到父 shell。这是交叉编译、buildroot 最常踩的坑。
6. 简单实操验证
运行
# 1. 设置环境变量并导出 export CFLAGS="-O3" # 查看当前进程环境 env | grep CFLAGS # 直接执行gcc,不会使用CFLAGS gcc test.c # make会读取CFLAGS make运行
# 在一个子shell里面设置变量,父shell不受影响 ( export CFLAGS="-O0"; make ) # 括号结束,回到当前shell,CFLAGS还是-O3
7. 总结梳理
- 环境变量机制是操作系统统一的,所有进程共享这套 "key-value 字符串" 传递机制,不存在多套独立环境变量存储。
- 变量的含义是各个工具社区约定的 :
CFLAGS、MAKEFLAGS只是约定好的名字,Linux 本身不知道它们是什么。工具内部调用getenv()按需读取感兴趣的变量名。- 进程隔离继承模型:父进程环境复制给子进程;子进程修改环境,不会影响父进程。buildroot 编译过程中各种临时环境变量,只在 buildroot 运行期间有效。
- 区分:系统通用环境变量 (PATH 等) vs 构建工具约定变量(CFLAGS、MAKEFLAGS 等)。
环境变量的作用范围
环境变量的作用范围,不太理解,进程之间,进程和子进程,不同的shell之间,环境变量有啥区别?
核心一句话
环境变量是属于「进程」的一份数据(key=value 列表)。 进程启动子进程时,会复制一份自己当前的环境变量给子进程;
✅ 子进程拿到的是副本 ,不是共享内存里的同一个对象。✅ 子进程修改副本,完全不会影响父进程。
这是所有范围问题的根源。下面分场景拆开讲:shell、子 shell、普通进程、多个独立 shell。
先区分两个容易混淆的东西:
- shell 局部变量 :只在当前 shell 内部,没有放到进程环境里,不加
export。子进程看不到。- 环境变量 :放到进程环境中,
export var,fork 出来的子进程能继承这份副本。
a=10 # shell局部变量,没有export,子进程看不到 export b=20 # b变成环境变量,保存在当前shell进程的环境列表,子进程可以继承
1. 父进程 ↔ 子进程(最重要)
模型:
- 父进程调用
fork(),创建子进程。- 子进程复制父进程的环境变量列表。
- 子进程随后调用
exec()加载新程序(gcc /make/buildroot 脚本)。- 父子进程环境变量相互独立。
举例子:父 shell:
export X=100 bash # 启动一个新的子shell进程子 shell 里面:
echo $X # 100,继承了父shell复制过来的副本 export X=200# 修改的是子shell自己这份副本 exit # 退出子shell回到父 shell:
echo $X # 仍然是100!子进程修改不会传回父进程关键点:继承 = 复制一份快照,不是共享。单向传递,不能回传。 所有命令:
gcc、make、脚本,都是这个模型。make 里修改环境变量,只影响 make 自己和 make 的子进程,不会改你外面的终端 shell。小坑:shell 脚本
# test.sh export A=999 echo $A情况 1:
bash test.sh
- 新建子进程运行脚本;脚本里 export A,只在这个子进程里。脚本结束,A 消失。父 shell 看不到 A。
情况 2:
source test.sh或者. test.sh
- 不创建新进程!脚本直接在当前 shell 进程执行。
- 脚本里 export 的变量,直接加到当前 shell 的环境,执行完脚本,变量保留在当前 shell。👉 这就是 buildroot 经常有人用
source envsetup.sh的原因!source 就是为了在当前 shell 修改环境变量,而不是子进程。
2. 同一份 shell 里:普通命令 vs 括号
(cmd)子 shell
export VAR=1 (VAR=2; echo $VAR) echo $VAR输出:
2 1
()会创建一个子 shell 进程,括号内所有操作在子 shell 副本里,修改不影响外层。对比大括号
{}:不会新建进程,就在当前 shell 执行
{ VAR=2; echo $VAR; } echo $VAR输出:
2 2
3. 多个独立的 shell 窗口(不同终端)
打开两个终端窗口:终端 A、终端 B。它们是两个完全独立的 bash 进程。
- 终端 A 里
export X=10,只属于 A 这个进程。- 终端 B 进程启动很早,不会自动同步 A 的环境变量。B 看不到 X。
哪怕同一个用户,不同终端 = 不同进程,环境变量互相隔离。只有新建 shell 的时候,会复制父进程当时的环境;之后两边互不影响。
👉 那为什么新开终端有时候自动有 PATH、HOME?因为新开终端启动 bash 时,bash 会读取配置文件:
~/.bashrc/~/.profile,在这个新 shell 进程启动时,执行脚本设置变量。不是别的 shell 同步过来的。区分:
- 运行时
export:只属于当前 shell 进程,关闭终端就丢。- 写在
.bashrc:每次新开终端(新建 bash 进程),启动时执行脚本,自动设置一遍。
4. 总结一张表
场景 是否新建进程 环境变量影响范围 在当前 shell: export X=1❌ 不新建进程 当前 shell 进程,以及之后启动的所有子进程;其他终端 shell 不受影响 bash script.sh执行脚本✅ 新建子进程 变量修改仅在脚本子进程内,退出全部消失,父 shell 不变 source script.sh❌ 不新建进程 脚本直接修改当前 shell 的环境变量,持久保留在当前终端 (cmd1; cmd2)括号子 shell✅ 新建子 shell 进程 括号内修改只在子 shell 副本,外面不变 { cmd1; cmd2; }大括号❌ 当前 shell 执行 修改直接作用当前 shell 新开一个终端窗口 ✅ 全新 bash 进程 启动时读取 bash 配置文件初始化环境;和旧终端环境互相独立 make /gcc 等程序 ✅ 作为 shell 的子进程运行 继承启动它时 shell 环境;程序内部修改环境只影响自身和它的子进程 5. 结合你之前编译场景(buildroot/make)举例
# 终端shell(父进程) export CFLAGS="-O2" make
- shell fork+exec 启动 make 进程。make 拿到一份环境副本,里面有
CFLAGS="-O2"- make 执行编译规则,fork 启动 gcc,gcc 继承 make 此时的环境副本
- make 在执行过程中,脚本修改环境变量(比如修改 PATH,切换交叉编译器)
- 这个修改只在 make 进程内部
- make 结束退出 → 所有 make 内部改动全部消失,回到你的终端 shell,环境还是最开始的样子
所以你执行完 buildroot make 之后,你的 shell 不会自动变成交叉编译环境。想要当前 shell 变成交叉编译环境,buildroot 一般提供脚本,需要你
source output/env.sh,在当前 shell 执行,而不是子进程。6. 几个高频误区
❌ "环境变量属于用户"✅ 错。环境变量属于进程。同一个用户,可以有很多进程,每个进程环境变量副本都可以不一样。
❌ "子进程修改环境变量,父进程能读到"✅ 错。fork 是复制快照。子进程修改副本,父进程那份原数据纹丝不动。这是操作系统设计,不是 shell 特性。
❌ export 之后,所有 shell 都能看到这个变量✅ 错。export 只把变量放到当前这个 shell 进程的环境。别的终端 shell 是独立进程,不会自动同步。只有这个 shell 后续创建的子进程才能继承。
❌ .bashrc 里写的变量,会立刻生效到已经打开的终端✅ 错。.bashrc 只在新建 bash 进程的时候 读取。已经打开的终端不会自动重读。修改.bashrc 后,要么
source ~/.bashrc,要么新开终端。7. 快速验证命令,你可以自己跑一遍感受
# 1. 父shell设置 export TEST_ENV="父进程的值" echo $TEST_ENV # 2. 启动子shell修改 bash -c "export TEST_ENV='子进程修改的值'; echo 子shell内:\$TEST_ENV" # 3. 回到父shell再看 echo 父shell:$TEST_ENV输出:
父进程的值 子shell内:子进程修改的值 父shell:父进程的值
如何查看各种环境变量
Linux 查看环境变量:常用命令,分清「当前 shell 环境」「进程环境」「Makefile 里的变量」
记住两个核心:
env/printenv:查看当前进程的环境变量(会被子进程继承的那些,export 出来的)set:查看当前 shell 所有变量,包含局部变量(没 export)+ 环境变量 + shell 内置变量,范围更大一、在当前终端 shell 查看(最常用)
1. 列出全部环境变量(推荐
env或printenv)
env # 或者 printenv输出一堆
KEY=VALUE,这些就是当前 bash 进程的环境变量,fork 出来的子进程(make/gcc)能继承。2. 只看某一个变量的值
printenv CFLAGS # 或者 echo $CFLAGS⚠️ 小区别:
echo $VAR:shell 先替换变量,打印。不管是不是环境变量,只要 shell 里有这个变量就能打印(包括未 export 的局部变量)printenv VAR:只读取环境变量。如果只是 shell 局部变量(没 export),printenv 拿不到,返回空。例子:
# 定义局部变量,不export TEST=123 echo $TEST # 123,shell知道 printenv TEST # 空!因为不是环境变量,子进程看不到 export TEST printenv TEST # 123,变成环境变量了3. set:查看 shell 全部变量(局部 + 环境 + shell 内置)
set输出非常多,包含 bash 内部变量、函数、未 export 变量。过滤:
set | grep CFLAGS简单区分记忆:
env:子进程能拿到的(环境变量)set:当前 shell 自己全部的东西(局部 + 环境)二、查看【另一个正在运行进程】的环境变量(重点!适合排查 make/buildroot)
你想知道:某个正在跑的 make /buildroot 进程,它里面的环境变量是什么?Linux 每个进程在
/proc/<pid>/environ保存它的环境变量。操作步骤:
找到进程 PID
ps aux | grep make
查看该进程环境(
environ里面用\0分隔,不好读,用 tr 替换换行)cat /proc/12345/environ | tr '\0' '\n'
12345 替换成真实 PID。✅ 这个非常有用:比如 buildroot 编译过程中,单独看 make 进程里真实的
CFLAGS、PATH,不是你终端 shell 的环境,是 make 子进程自己那份副本 。⚠️ 进程退出之后,/proc/pid 文件夹直接消失,只能进程运行期间查看。三、查看子进程里的环境,一次性测试(不用手动查 pid)
想验证:启动 make/gcc 的时候,子进程拿到的环境到底有哪些?直接在命令前面加
env,或者用 bash 子 shell 打印:
# 方式1:启动子shell,打印环境,模拟子进程看到的 bash -c env # 方式2:看子进程里的CFLAGS bash -c 'printenv CFLAGS'举个编译场景例子:
export CFLAGS="-O2" make # 新开子进程,看看make能读到什么环境 bash -c 'env | grep CFLAGS'四、Makefile 里面的变量:坑最多!
Makefile 里的变量分两类:
- make 内部变量 :只在 make 程序内部,默认不会变成环境变量传给子命令,除非你显式 export(Makefile 里的 export)
- make 导出的环境变量:make 传给它 fork 出来子进程(gcc 等)
在 Makefile 里打印变量,两种方式:
方式 1:Makefile 内部打印(看 make 自身变量)
all: @echo "CFLAGS inside make: $(CFLAGS)"这是 make 把变量展开,传给 shell 打印;不等于一定传给 gcc 子进程。
方式 2:在 Makefile 中,让子进程打印环境变量(看 gcc 真正拿到的)
all: env | grep CFLAGS
env是 make 启动的子进程运行的命令,打印的是子进程的环境变量 。如果 Makefile 没有export CFLAGS,哪怕 make 内部有 CFLAGS,子进程 env 也看不到!Makefile 里的 export 语法(把 make 变量提升为环境变量,传给子进程)
# Makefile export CFLAGS := -O2 all: env | grep CFLAGS快速测试 Makefile 变量小脚本
新建 Makefile:
TEST_VAR=hello # export TEST_VAR all: @echo "make内部 TEST_VAR = $(TEST_VAR)" env | grep TEST_VAR || echo "子进程环境里没有 TEST_VAR"
- 不打开 export:子进程 env 看不到 TEST_VAR
- 打开 export:子进程 env 就能看到 TEST_VAR
五、buildroot 场景小技巧
buildroot 编译时,你想看它传给工具链的环境:
- 可以临时修改 buildroot 的 Makefile,加一行
env > log.env,把编译时的环境全部输出到文件;- 或者在 buildroot 执行过程中,用
ps找到 make 的 pid,/proc/<pid>/environ查看。另外:buildroot 的
output/env.sh,这个脚本source之后,会在你当前 shell 设置一套交叉编译环境变量,你 source 完可以直接env查看。六、常用命令对比表
命令 查看范围 是否包含未 export 局部变量 env 当前进程环境变量(子进程可继承) ❌ printenv 当前进程环境变量 ❌ echo $VAR shell 变量,优先取环境变量 ✅(局部变量也能打印) set 当前 shell:局部变量 + 环境变量 + shell 函数 ✅ cat /proc/<pid>/environ 指定进程的环境变量 ❌ 七、常见踩坑
echo $CFLAGS有值,但 make 启动的 gcc 子进程没有:大概率变量没有 export,只是 shell 局部变量。- 终端
env看到 CFLAGS,但 make 编译出来的程序没有生效:可能是Makefile 内部重新覆盖了 CFLAGS,或者 buildroot 脚本覆盖,此时要看 make 进程内部的环境(/proc/pid)。- 在 Makefile 里
echo $(VAR)看到值,但是env看不到:make 变量没有 export 到子进程环境。