目录
- [1. 基本概念](#1. 基本概念)
- [2. 常见环境变量](#2. 常见环境变量)
- [2.1 环境变量是从哪里来的](#2.1 环境变量是从哪里来的)
- [3. 环境变量的作用](#3. 环境变量的作用)
- [3.1 自己的程序直接运行报错](#3.1 自己的程序直接运行报错)
- [3.2 PATH 环境变量](#3.2 PATH 环境变量)
- [3.3 解决报错](#3.3 解决报错)
- [4. 环境变量相关的命令](#4. 环境变量相关的命令)
- [4.1 echo:显示某个环境变量的值](#4.1 echo:显示某个环境变量的值)
- [4.2 export:设置一个新的环境变量](#4.2 export:设置一个新的环境变量)
- [4.3 env:显示所有环境变量](#4.3 env:显示所有环境变量)
- [4.4 unset:清除环境变量](#4.4 unset:清除环境变量)
- [4.5 set:显示本地定义的 shell 变量和环境变量](#4.5 set:显示本地定义的 shell 变量和环境变量)
- [5. 环境变量的调用获取](#5. 环境变量的调用获取)
- [5.1 环境变量的内存结构](#5.1 环境变量的内存结构)
- [5.2 程序获取环境变量](#5.2 程序获取环境变量)
- [5.2.1 main 函数的第三个参数](#5.2.1 main 函数的第三个参数)
- [5.2.2 全局变量 environ](#5.2.2 全局变量 environ)
- [5.2.3 getenv()](#5.2.3 getenv())
- [6. 环境变量的全局性](#6. 环境变量的全局性)
- [6.1 模拟进程间的环境变量传递](#6.1 模拟进程间的环境变量传递)
1. 基本概念
-
定义 :环境变量一般是指在操作系统中用来指定操作系统运行环境的一些参数。
- 通俗理解:你可以把它想象成操作系统的"全局设置"或"公共便签"。系统里的各种程序在运行的时候,都会去查看这些便签,以获取自己需要的信息(比如去哪里找文件、用什么语言显示等)。
-
示例:
-
我们在编写 C/C++ 代码并链接时,往往不知道链接的动态库/静态库具体在哪个文件夹,但依然能链接成功。
-
原因 :这是因为系统里有相关的环境变量(比如
LIBRARY_PATH等)。编译器会读取这些环境变量,环境变量告诉编译器:"去这些指定的文件夹里找库文件"。这就好比你把书放在了家里的某个柜子里,你不需要每次找书都记住具体位置,只需要看墙上的"物品存放指南"(环境变量)就能找到。 -
以Windows为例:

-
-
特点:
-
特殊用途:每个环境变量都有其特定的使命。
-
全局特性:环境变量通常对当前系统或当前用户下的所有程序都生效(具有全局性),而不是只针对某一个单独的程序。
-
2. 常见环境变量
-
PATH:指定命令的搜索路径。- 作用 :当你在终端输入一个命令(比如
ls、python)时,系统并不知道这个程序存放在硬盘的哪个角落。系统会去PATH变量所包含的那些目录里挨个寻找。找到了就执行,找不到就会报错command not found。
- 作用 :当你在终端输入一个命令(比如
-
HOME:指定用户的主工作目录。- 作用 :也就是用户登录到 Linux 系统时,默认进入的那个目录(通常形如
/home/用户名)。当你输入cd ~或者很多程序需要保存用户配置文件时,都会用到这个变量。
- 作用 :也就是用户登录到 Linux 系统时,默认进入的那个目录(通常形如
-
SHELL:当前 Shell。- 作用 :它指定了你当前正在使用的命令行解释器是什么。在绝大多数 Linux 系统中,它的值通常是
/bin/bash。不同的 Shell(如 sh, zsh, csh)功能和语法略有不同,程序可以通过读取这个变量来知道自己处于什么命令环境下。
- 作用 :它指定了你当前正在使用的命令行解释器是什么。在绝大多数 Linux 系统中,它的值通常是
2.1 环境变量是从哪里来的
-
源头一:配置文件
当我们打开终端时,系统会去读取特定的配置文件 (比如
/etc/profile、~/.bashrc等)。这些配置文件就像是系统的"备忘录",系统启动时会执行里面的脚本,从而生成我们的环境变量。 -
源头二:父进程的传递
当你打开终端,此时运行的是一个
bash进程。**bash 进程的环境变量,**进程之间是有"父子关系"的。当前 bash 的环境变量,是由它的父进程(比如系统启动时的登录管理器)传递过来的;而父进程又是从更上级继承的。
3. 环境变量的作用
3.1 自己的程序直接运行报错
-
创建了一个程序 :用户通过
ls -l查看当前目录,创建有一个名为mycommand的文件(绿色的字,且有x执行权限-rwxr-xr-x)。手写笔记指出,这是一个用户自己实现的命令(执行后输出"我是一个命令,自己实现的!")。

-
成功执行(带路径) :用户输入
./mycommand,程序成功运行了。./就是相对路径,本质告诉操作系统(OS),用户要执行的 bin(可执行文件),就在当前路径 下。
-
报错(不带路径) :用户接着直接输入
mycommand,系统报错:-bash: /usr/bin/mycommand: No such file or directory,最终提示command not found。
核心疑问提出 :"Linux 它自己怎么知道我应该去哪一个路径下找可执行程序啊?"
3.2 PATH 环境变量
Linux系统,会存在所谓的 环境变量的东西,PATH。
查看 PATH 的值 :用户输入了命令 echo $PATH。
这条命令的意思是------"告诉Linux系统,如果用户执行一个可执行文件,没有指明路径(比如直接敲 mycommand)" ,系统就按照 PATH 后面列出的这些路径,挨个去里面找。
PATH 里的内容 :输出结果是一串用冒号 : 隔开的目录列表(如 /usr/local/bin, /usr/bin, /home/whb/bin 等)。
为什么报错 :因为用户的 mycommand 程序存放在他当前的文件夹,而这个当前目录并没有被写进 PATH 这个环境变量里面 。所以系统去 /usr/bin 等地方找了一圈没找到,只能报错 command not found。
3.3 解决报错
临时添加当前路径到 PATH 环境变量
export PATH=$PATH:.

这次它成功运行了! 因为系统现在会在当前目录下找命令了。
因为刚才用了 export 是临时修改,如果你关掉这个终端窗口,或者重启系统,环境变量就会恢复原样,你的 mycommand 又得加 ./ 才能运行了。要永久生效,需要把 export PATH=$PATH:. 写进 ~/.bashrc 文件里,但是不建议这么做。
4. 环境变量相关的命令
4.1. echo:显示某个环境变量的值
-
作用 :读取并打印指定变量的内容。在 Linux 中,要获取变量的值,需要在变量名前面加上
$符号。如果只是敲变量名,系统会把它当成普通字符串输出。 -
命令格式:
echo $变量名 -
示例:
# 查看 PATH 环境变量的具体内容
$ echo $PATH
查看当前使用的 Shell 类型
$ echo $SHELL
查看当前登录的用户名
$ echo $USER

4.2. export:设置一个新的环境变量
-
作用 :用于定义一个新的环境变量,或者修改已有环境变量的值。非常重要的一点 :使用
export设置的变量,不仅在当前 Shell 中有效,还会传递给由当前 Shell 启动的子进程(使其具有全局属性)。 -
命令格式:
export 变量名=变量值 -
示例:

4.3. env:显示所有环境变量
-
作用 :
env是 environment 的缩写。它用于列出系统中当前所有的环境变量及其值。通常这个命令输出的内容会非常多,我们经常配合grep命令来查找特定的变量。 -
命令格式:
env 或 env | grep 关键字 -
示例:

4.4. unset:清除环境变量
-
作用 :删除指定的变量(包括环境变量和普通变量)。清除后,再次用
echo查看该变量,将输出空白。 -
命令格式:
unset 变量名 -
示例:

4.5 set:显示本地定义的 shell 变量和环境变量
-
作用 :这是一个比较全能的命令。
env只显示环境变量,而set会显示所有的变量 ,包括环境变量、用户自定义的普通变量(局部变量)以及 Shell 内置的函数。(如果不加
export直接定义变量,如A=1,它只是一个本地变量,不会传递给子进程。用set能看到它,但用env看不到。) -
命令格式:
set 或 set | grep 关键字 -
示例:

5. 环境变量的调用获取
5.1 环境变量的内存结构

环境变量本质上就是一个字符串数组。系统把所有的环境变量打包成这种格式,存在内存里。
环境变量怎么传给我们的程序的:
本质是把环境变量表,传递给进程 ,我的程序,命令等,都是bash进程的子进程。
解释 :你在终端敲下 ./mycommand,其实是你当前所在的 bash 进程用 fork() 函数生成子进程,这个子进程就是你的 mycommand 程序。在生儿子的过程中,bash 把自己内存里的"环境变量表"原封不动地复制(传递)给了子进程。
5.2 程序获取环境变量
5.2.1 main 函数的第三个参数
#include <stdio.h>
int main(int argc, char *argv[], char *env[])
{
int i = 0;
// env 数组以 NULL 结尾,利用这个特性遍历打印
for(; env[i]; i++){
printf("%s\n", env[i]);
}
return 0;
}


main 函数是可以带参数的!除了 argc(参数个数)和 argv(参数数组),第三个参数 env 就是环境变量表 。它是一个字符串数组,里面存满了 KEY=VALUE 这样的字符串,最后以 NULL 结束。
5.2.2 全局变量 environ
#include <stdio.h>
int main(int argc, char *argv[])
{
extern char **environ; // 重点在这里!
int i = 0;
for(; environ[i]; i++){
printf("%s\n", environ[i]);
}
return 0;
}


-
libc中定义的全局变量environ指向环境变量表,environ没有包含在任何头文件中,所以在使用时要用 extern 声明。
-
意思是,C 标准库(libc)偷偷在内存里维护了一个指针,叫
environ。只要你用extern声明一下,就能直接用它遍历环境变量,连main函数的参数都不用写。
5.2.3 getenv()
#include <stdio.h>
#include <stdlib.h> // getenv 在这个头文件里
int main()
{
printf("%s\n", getenv("PATH"));
return 0;
}


前面两种方法都是把所有 环境变量都打印出来。但大多数时候,我们只关心某个特定的变量(比如 PATH)。这时候用 getenv("变量名") 最方便,它会返回变量的值,如果找不到就返回 NULL。
6. 环境变量的全局性
#include <stdio.h>
#include <stdlib.h>
int main()
{
char *env = getenv("MYENV");
if(env){
printf("%s\n", env);
}
return 0;
}

-
直接运行 :代码啥也不输出。说明当前系统的环境变量里根本没有
MYENV这个东西。
-
导出变量 :在终端里敲
export MYENV="hello world"。 -
再次运行 :程序输出了
hello world!
为什么程序能读到:
-
因为你的程序是
bash的子进程 。你在bash(父进程)里export了一个变量,这个变量就被写入了bash的内存环境变量表中。 -
当你运行程序时,
bash通过fork()生成了子进程,并把它的环境变量表拷贝了一份给子进程。 -
结论:环境变量可以被子进程继承,这就是它具有"全局属性"的根本原因。
6.1 模拟进程间的环境变量传递
展示 fork() 和 exec 函数在底层是如何传递环境变量的。

-
手动构建数组 :代码开头手动模拟了系统给我们传递参数的样子。把
argv设置成了ls -a,把env设置成了只有一条PATH=AAAA的列表。 -
发生
fork():程序走到fork()时,系统会创建一个子进程。-
子进程中(
id == 0) :通过for循环遍历argv和env数组并打印。 -
父进程中(
id > 0) :执行了sleep(10000),也就是挂起不动。
-
-
底层逻辑 :这说明当父进程派生出子进程时,子进程会复制 父进程的内存空间。你在父进程里修改了
env数组的值,子进程里拿到的也是你修改后的值。这是execve替换进程映像时的核心准备工作。
