本节重点
- 理解环境变量的基本概念
- 学会在指令和代码操作上查询更改环境变量
- 环境变量表的基本概念
- 父子进程间环境变量的继承与隔离
一、引入
1.1 自定义命令(我们的exe)
我们以往的Linux编程经验告诉我们,我们在对一段代码编译形成可执行文件后执行可执行文件必须声明可执行文件所在的路径,路径可以是相对路径也可以是绝对路径:
而如果我们不加上 ./ 也就是不加上可执行文件的完整路径,系统就会出现以下报错:
表示命令行解释器(bash)无法找到该命令的可执行文件和该命令的实现方法。
1.2系统命令的实现:
系统命令的实现与上述别无二致,其主要步骤如下:
a、开发者使用编程语言(如C、C++、Shell等)编写命令的源代码。源代码包含了实现命令功能的逻辑和算法。
b、使用编译器(如gcc)将源代码编译成目标文件(.o文件),然后将目标文件链接成可执行文件。在链接过程中,编译器会链接所需的库文件,确保命令能够正常运行。
c、将编译生成的可执行文件安装到系统的某个目录中,通常是/usr/bin
、/sbin
、/usr/sbin、/
等标准目录中。
所以系统命令本质上也是可执行文件,当我们在命令行输入系统命令如 ls 、cd、touch、mkdir时,bash(命令行解释器)会将命令进行解析检查命令是否合法,当合法时bash就会创建子进程来执行该命令。
对于系统命令来说,bash(命令行解释器)会自动在/usr/bin
、/sbin
、/usr/sbin、/bin
等标准目录中查找相应命令的可执行文件(也就是实现方法),找到之后执行可执行文件即可。
而对于我们“自定义的命令”也就是我们的可执行文件来讲,当我们输入可执行文件的文件名时bash也会自动在/usr/bin
、/sbin
、/usr/sbin、/bin
等标准目录中查找对应的可执行文件,结果当然是找不到所以就会报错(Command not found)。
1.3 引入环境变量
当我们登录Linux系统时bash进程就会被创建,同时bash进程会从系统文件中配置并维护一批环境变量来指定运行环境参数,其中 PATH 就是其中之一,其指定了/usr/bin、/sbin、/usr/sbin、/bin等标准目录:
当用户在命令行或脚本中输入一个命令或程序名时,操作系统会根据PATH环境变量的值去查找该命令或程序的可执行文件,如果找到了相应的可执行文件,操作系统就会执行它;如果没有找到,操作系统会报错,提示“命令无法识别”或类似的错误信息。
二、基本概念
在Linux系统中,环境变量是一种用于存储影响进程行为和运行的动态值的机制。环境变量是由操作系统或用户设置的,可以被系统和应用程序在运行时访问和使用。它们通常用于定义系统配置、路径设置、用户偏好等。
格式:
环境变量名 = 内容
如环境变量PATH:
一个环境变量可能会有多个内容(值),不同内容(值)之间用 :隔开。
在命令行界面我们可以使用 env 来查看所有环境变量以及内容:
如:我们在编写C/C++代码的时候,在链接的时候,从来不知道我们的所链接的动态静态库在哪里,但是照样可以链接成功,生成可执行程序,原因就是有相关环境变量帮助编译器进行查找。
三、获取环境变量
3.1 指令操作
1> env
功能:查看所有或特定的环境变量
#查看所有的环境变量
env
#查看特定的环境变量(如PATH)
env | grep PATH
示例:查看特定的环境变量HOME
2> export
功能:设置或者更改环境变量
设置一个环境变量:
export 变量名=内容
示例:设置一个新环境变量value
export value=/home/yjh/linux-learning
更改一个环境变量的内容(临时):
全部更改:
export 变量名=新内容
追加更改:
export $变量名=:新内容
示例:在环境变量PATH之后再追加一段路径/home/yjh/linux-learning
export $PATH:/home/yjh/linux-learning
3> echo
功能:查看环境变量的内容
echo $变量名
示例:查看环境变量PATH的内容
4> unset
功能:删除某一个环境变量
unset 变量名
示例:
3.2 代码操作
除了在命令行窗口用指令获取环境变量的内容,在代码之中我们也可以通过getenv等库函数获得对应环境变量的内容,以便于我们配置和管理运行时行为。
1> 通过父进程(bash等)获取
在理解这个概念之前我们需要明白的是main函数是什么,main函数的参数究竟有几个,main函数被谁调用这几个问题,下面我们对这些问题一一进行解答:
首先main函数是我们C程序的唯一入口点,操作系统会加载可执行文件,并寻找main函数作为执行的起点。在C语言中main函数有以下两种标准形式:
- int main( void )
- int main( int argc , char * argv[] )
第一种是我们常见的无参形式即不需要传递参数,第二种接受命令行参数,其中argv是一个字符串数组,而argc表示该数组中的元素个数,关于命令行参数的相关知识我们后续实现自定义shell(命令行解释器)时会给大家一一讲解。
其实在C标准中,main函数还支持第三个参数即环境变量表,所以C标准允许main函数所定义的形式为:
int main(int argc,char* argv[],char* env[])
{return 0;
}
其中env是一个指向环境变量字符串数组的指针,每个字符串格式为 NAME=value 。不过,这个参数在C标准中是可选的,不是所有实现都支持。例如,在Windows的某些编译器中,可能不支持env 参数,或者需要通过其他方式获取环境变量。
这里我们来写一个简单的代码打印验证一下我们通过main函数获取的环境变量:
#include<stdio.h>
int main(int argc,char* argv[],char* env[])
{(void)argc;(void)argv;for(int i=0;env[i];i++){//打印数组中的每一个元素printf("%s\n",env[i]); }printf("\n");return 0;
}
运行结果:
其中这个env字符串数组还有一个名字就是环境变量表。前面我们说过当我们登录Linux系统时系统就会给我们自动创建一个bash进程,之后我们进行的一系列命令,写的一系列代码运行起来都是bash的子进程,当我们的C程序进程被创建时bash就会调用一系列系统调用将环境变量传递给子进程形成一张环境变量表,子进程通过char* env[ ] 参数或全局变量 extern char** environ访问这些环境变量。
举个例子,当我们在bash中对环境变量进行一系列增删查改时,结果也可以在子进程(我们的C程序)中看到:
设置一组新环境变量:
export value1=123456
export value2=yjh
export value3=jzq
export value4=hello
此时再打印我们C程序中的环境变量表内容发现也可以看到新增环境变量:
2> getenv
getenv 是一个在C标准库(<stdlib.h>)中定义的用于获取环境变量值的函数
函数原型:
char *getenv(const char *name);
功能:接受一个字符串name表示要查询的环境变量的名称,成功返回 char* 类型指向该环境变量的指针(字符串),失败返回NULL(变量不存在或不可访问)。
底层原理:全局变量environ(extern char** environ)
上面我们说过,当我们的C程序的进程被创建时,bash就会通过调用系统调用将环境变量传递给子进程(我们的C程序),形成一张环境变量表而子进程可以通过参数env或者environ来接受,其中environ是一个指向环境变量表(字符串数组)的指针。
而getenv函数内部通过遍历environ查找匹配的变量名,并返回对应环境变量的内容。
示例:查询环境变量PATH的内容
#include<stdio.h>
#include<stdlib.h>int main()
{char* value=getenv("PATH");if(value==NULL){printf("getenv fail\n");}else{printf("PATH->%s\n",value);}return 0;
}
运行结果:
3> environ指针
在Linux系统中,environ是一个全局变量,指向环境变量字符串数组的指针。它允许程序直接访问系统环境变量(如PATH、HOME等)。
类型:extern char** environ(C语言中声明为全局变量)
内容:每个字符串格式为 name=value 例如:PATH=/usr/local/sbin
用途:程序可通过 environ 直接读取或修改环境变量
示例:
使用environ打印所有的环境变量:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
extern char** environ;
int main()
{for(char **env=environ;*env!=NULL;env++){printf("%s\n",*env);}return 0;
}
运行结果:
四、环境变量表
4.1 基本概念
在Linux系统中环境变量表实际上指的是存储环境变量的数据结构其本质上是一个字符串数组,它在程序启动时从父进程中被继承并在程序运行过程中可以被访问和修改。
4.2 环境变量表的产生
4.2.1 子进程中拷贝自父进程
在子进程中的环境变量表由父进程传递而来,也就是说当子进程被创建时父进程会拷贝一份自己的环境变量表传递给子进程,这里我们可以做一个小示例:
当我们在父进程(这里是bash)中对环境变量表进行一系列增删查改后,此时我们运行自己的C程序(创建bash的子进程)会发现子进程中的环境变量表也发生了改变。
在bash(父进程)设置一组新环境变量:
export value1=123456
export value2=yjh
export value3=jzq
export value4=hello
此时打印我们C程序(子进程)中的环境变量表内容发现也可以看到新增环境变量:
注意:子进程中修改环境变量表不会影响父进程
也就是说父子进程中的环境变量表都是独立的副本,子进程被创建时父进程会拷贝自己的环境变量表加载到子进程,而子进程对自己环境变量表的修改不会影响到父进程,只会影响当前的子进程及其后续创建的子进程。
4.2.2 父进程(bash)中来自系统配置
在前面我们了解到,子进程中的环境变量表由父进程中拷贝而来,而父进程(bash)中的环境变量表从何而来呢?答案是从系统配置文件而来。
在前面我们学习了一系列指令用于在命令行窗口中设置或更改环境变量,例如export和unset指令,但实际上这些操作都具有临时性,当我们退出系统并重新登录时,就会发现我们修改的结果并没有永久生效,系统会重新生成一份全新的默认的环境变量表:
在bash中新增一组环境变量:
export value1=123456
export value2=yjh
export value3=jzq
export value4=hello
此时我们可以看到新增后的结果:
当我们退出系统重新登录Linux后执行env会发现新增的一组环境变量不见了:
bash中的环境变量来源于系统级或者用户级配置文件如
~/.bashrc
、~/.bash_profile
等,export或unset指令并没有对系统配置文件进行修改所以其更改具有临时性,当我们再次登录时系统就会根据配置文件生成全新的默认的环境变量表。
五、总结
在这次学习中我们首先通过自定义可执行文件与系统指令运行时的差异上引出了环境变量的概念,之后我们了解了PATH环境变量的作用:命令行解释器(shell)通过PATH的内容遍历标准目录来查找相关指令的可执行文件,找到就运行文件找不到就报Command not found 等类似错误。
之后我们分别在指令操作与代码操作两个方面总结了查询或更改环境变量的指令与函数,在这里我们提到了环境变量的”继承性“与差异,即当子进程创建时父进程就会拷贝一份自己的环境变量表加载到子进程,而子进程对自己环境变量表的更改不会影响到父进程。
最后我们谈到了环境变量表的由来:即子进程中由父进程拷贝并加载而来,父进程(bash)中则来源于系统的配置文件。