【Linux操作系统】进程篇(七):环境变量:程序运行时的那些信息,到底从哪里来? 一、先认识环境变量它到底是什么先建立一个流程接下来细节理解我们平时使用 Linux 的时候经常会输入各种命令比如lspwdgcc这些命令都可以直接运行。但是当我们自己写了一个 C 语言程序编译之后却经常需要这样运行./test为什么系统自带的命令可以直接运行而我们自己写的程序却需要加上./呢其实这就涉及 Linux 中一个很重要的概念环境变量。环境变量说白了就是程序运行时需要的一些环境信息。比如当前用户是谁、家目录在哪里、系统应该去哪些目录寻找可执行程序等。这些信息会影响程序的运行所以操作系统需要有一种方式把这些信息提供给程序使用。我们可以先把环境变量理解成程序运行时随身携带的一份环境信息。这里还要区分一个概念普通变量和环境变量。比如我们在 Shell 中定义一个普通变量nameYi这时候当前 Shell 可以使用这个变量echo$name但是普通变量并不会自动传递给之后创建的子进程。如果我们使用exportname就相当于把这个变量导出为环境变量之后创建的子进程也可以获得它。所以两者最主要的区别是普通变量主要在当前 Shell 中使用。环境变量除了当前 Shell 使用还可以传递给之后创建的子进程。理解这一点后面学习环境变量的继承就容易多了。二、环境变量都有哪些先认识几个常见的Linux 中有很多环境变量不同的环境变量保存着不同的信息。我们先认识几个比较常见的。1. PATH系统去哪里找命令这是我们最需要理解的一个环境变量。比如我们平时输入ls系统为什么能够找到ls呢因为 PATH 中保存了一些目录Shell 会根据这些目录去寻找对应的可执行程序。我们可以通过下面的命令查看echo$PATH可能会看到类似这样的结果/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin这里有很多目录它们之间使用冒号:分隔。也就是说PATH 保存的不是某一个程序而是一系列目录。当我们输入命令时Shell 就可以根据 PATH 中的目录去寻找对应的程序。2. HOME当前用户的家目录echo$HOME它保存的是当前用户的家目录。比如/home/ubuntu当我们输入cd~就可以回到当前用户的家目录。3. USER当前用户名echo$USER它保存当前用户的用户名。例如ubuntu4. PWD当前所在目录echo$PWD它保存当前 Shell 的工作目录。比如/home/ubuntu这些环境变量保存的信息不同但是作用都是为程序提供运行环境所需要的信息。三、环境变量怎么使用先从 Linux 命令开始认识了环境变量之后我们再来看看怎么查看、设置和删除它们。1. echo查看指定环境变量如果我们只想查看某一个环境变量可以使用echo$PATH这里的$表示取出变量对应的值。比如echo$HOME就可以查看当前用户的家目录。2. env查看环境变量列表如果我们想一次性查看当前 Shell 中导出的环境变量可以使用env它会把当前环境中的环境变量打印出来。例如USERubuntu HOME/home/ubuntu PATH/usr/local/sbin:/usr/local/bin:/usr/bin PWD/home/ubuntu这样就能看到当前环境中有哪些变量。3. export设置并导出环境变量比如我们想创建一个自己的环境变量exportMY_NAMEYi然后查看echo$MY_NAME就能得到Yi这里要注意export不仅可以设置变量还可以把原本的普通 Shell 变量导出为环境变量。例如MY_NAMEYiexportMY_NAME这两条命令和直接使用export MY_NAMEYi的效果类似。4. unset删除环境变量如果我们不再需要某个变量可以使用unsetMY_NAME再查看echo$MY_NAME就不会再得到原来的值了。到这里我们已经掌握了环境变量最基本的操作命令作用echo $变量名查看指定变量env查看环境变量列表export设置或导出环境变量unset删除变量四、重点理解 PATH为什么有些命令不用 ./ 就能运行前面认识了 PATH现在我们单独拿它出来理解一下。我们先看一个实际场景。假设我们写了一个简单的 C 程序#includestdio.hintmain(){printf(hello Linux!\n);return0;}编译gcc test.c-otest运行./test这里为什么要加./呢因为我们输入命令时Shell 需要知道去哪里寻找对应的可执行程序。比如我们直接输入lsShell 会根据 PATH 中的目录去寻找ls。但是我们自己编译出来的test一般放在当前目录而当前目录通常不在 PATH 中。所以直接输入testShell 就可能找不到这个程序。而./test这里的.表示当前目录./test就是在明确告诉 Shell去当前目录寻找 test 程序。这就是两种运行方式的区别。动手实验把自己的程序加入 PATH既然 PATH 决定了 Shell 去哪里寻找程序那么我们能不能把自己的程序所在目录也加入 PATH 呢当然可以。比如当前程序放在/home/ubuntu/test我们可以执行exportPATH$PATH:/home/ubuntu这里$PATH保留原来的搜索目录。:分隔不同目录。/home/ubuntu新增一个搜索目录。这样Shell 就可以在/home/ubuntu中寻找可执行程序了。如果程序名是test就可以直接输入test不需要再写./test。这也说明了 PATH 的作用它只是告诉 Shell 去哪些目录寻找命令并不是把程序本身放进 PATH。五、程序也能获取环境变量吗前面我们都是在 Linux 命令行中操作环境变量。那么问题来了如果我们自己写了一个 C 语言程序能不能在程序内部获取环境变量呢当然可以。C 语言提供了几个常用函数。1. getenv()获取环境变量这个函数的作用就是根据环境变量的名字获取对应的值。例如#includestdio.h#includestdlib.hintmain(){char*pathgetenv(PATH);if(path!NULL){printf(%s\n,path);}return0;}这里getenv(PATH);就是获取 PATH 环境变量的值。如果找到了就返回对应字符串的地址如果没有找到就返回NULL。所以我们在使用时最好先判断一下返回值是否为空。2. setenv()设置环境变量如果我们想在 C 程序中设置一个环境变量可以使用setenv(MY_NAME,Yi,1);这里三个参数分别表示MY_NAME环境变量的名字。Yi环境变量的值。1如果变量已经存在允许覆盖原来的值。如果最后一个参数是0则表示变量已经存在时不覆盖。3. unsetenv()删除环境变量如果想删除一个环境变量可以使用unsetenv(MY_NAME);这样就可以删除当前进程环境中的这个变量。这里需要注意这些函数修改的是当前进程自己的环境不会直接修改父进程的环境变量。通过这几个函数我们就可以在 C 语言程序中获取、设置和删除环境变量了。六、环境变量在程序中是怎么存放的前面我们已经知道程序可以通过getenv()获取环境变量。但是这里又有一个问题环境变量那么多程序到底是怎么把它们组织起来的呢其实它们是通过一张环境变量表来组织的。比如我们可以把环境变量想象成下面这样环境变量表 | |---- PATH/usr/bin:/bin | |---- HOME/home/ubuntu | |---- USERubuntu | |---- PWD/home/ubuntu | |---- NULL可以看到环境变量表中保存着一个个字符串的地址每个字符串都包含变量名和对应的值。而且最后还有一个NULL表示环境变量表已经结束。那么在 C 语言中我们怎么访问这张表呢这里就要认识一个东西environ。environ 是什么在 Linux 中我们可以通过下面的方式声明externchar**environ;environ是一个全局变量它指向当前进程的环境变量表。这里为什么使用char**呢我们拆开来看char*可以指向一个字符串。char**可以指向一个存放字符串指针的数组。而环境变量表刚好就是由多个字符串指针组成的所以使用char**来表示。我们可以通过下面的代码遍历环境变量表#includestdio.hexternchar**environ;intmain(){inti0;while(environ[i]!NULL){printf(%s\n,environ[i]);i;}return0;}这里的逻辑很简单environ指向环境变量表。environ[i]取出其中一个环境变量字符串。使用printf打印。遇到NULL就停止遍历。这样就能把当前进程的环境变量全部打印出来。七、main 函数的第三个参数envp前面我们学习命令行参数时接触过argc和argv。比如intmain(intargc,char*argv[])其中argc命令行参数的个数。argv命令行参数表。例如我们执行./test hello world那么参数大致是argc 3 argv[0] ./test argv[1] hello argv[2] world argv[3] NULL注意最后的NULL不计算在argc中。那么 main 函数其实还可以有第三个参数intmain(intargc,char*argv[],char*envp[])这个envp就是环境变量表。也就是说程序启动时除了可以接收到命令行参数还可以接收到环境变量。我们可以通过下面的代码验证#includestdio.hintmain(intargc,char*argv[],char*envp[]){inti0;while(envp[i]!NULL){printf(%s\n,envp[i]);i;}return0;}运行之后就可以看到程序接收到的环境变量。这里我们可以把三个参数放在一起理解参数作用argc命令行参数的个数argv命令行参数表envp环境变量表其中argv和envp都是字符串指针数组并且都以NULL作为结束标志。它们保存的内容不同但组织形式很相似。这里补充一点第三个参数envp是 Linux 等系统中常见的用法并不是 ISO C 标准规定的 main 函数参数形式。八、环境变量为什么能被子进程继承前面我们知道了环境变量表也知道了程序可以通过envp获取环境变量。但是还有一个问题我们在 Shell 中设置的环境变量为什么自己运行的 C 程序也能获取到这就涉及父进程和子进程了。假设现在有一个 Shell 进程我们执行exportMY_NAMEYi这时MY_NAME就成为了 Shell 的环境变量。接下来我们执行./testShell 会创建子进程来执行这个程序。那么子进程能不能获得父进程中的MY_NAME呢答案是可以的。因为子进程在创建时会继承父进程的环境。所以在test程序中我们可以直接使用getenv(MY_NAME);获取到Yi子进程修改环境变量会影响父进程吗这里还有一个容易混淆的地方。既然子进程可以继承父进程的环境变量那么子进程修改环境变量是不是也会改变父进程的环境变量呢答案是不可以。因为子进程获得的是自己的一份环境数据。例如父进程中exportMY_NAMEYi子进程中通过setenv()把MY_NAME改成其他值。这个修改只影响子进程自己的环境不会反过来改变父进程中的变量。所以我们可以记住父进程可以把环境变量传递给子进程但是子进程对自己环境变量的修改不会直接影响父进程。这也是环境变量继承的一个重要特点。九、把整个知识点串起来到这里我们已经学习了环境变量的定义、常见命令、C 语言接口以及环境变量在进程之间的继承。最后我们把这些知识联系起来。1. 环境变量从哪里来环境变量可以由系统、Shell 或程序设置。比如我们在 Shell 中执行exportMY_NAMEYi就为当前 Shell 设置了一个环境变量。2. 环境变量怎么被程序使用程序启动时可以获得环境变量表。我们可以通过getenv()根据名字获取某个环境变量。environ访问当前进程的环境变量表。envp通过 main 函数的第三个参数获取环境变量表。3. 环境变量怎么传递当 Shell 创建子进程运行程序时子进程会继承父进程的环境变量。所以我们在 Shell 中设置的环境变量程序也能够使用。4. 最后记住这条知识线Shell 设置环境变量 | v export | v Shell 环境变量表 | v 创建子进程并执行程序 | v 子进程继承环境变量 | v 程序通过 getenv / environ / envp 获取总结一下环境变量本质上就是一组保存程序运行环境信息的变量。Shell 可以设置和管理它们程序可以通过 C 语言提供的接口获取它们而子进程又可以继承父进程的环境变量。从最开始的PATH到后面的getenv()、environ和envp其实讲的都是同一件事环境变量如何产生、如何保存以及如何被程序使用。