基本概念
环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数
如:我们在编写C/C++代码的时候,在链接的时候,从来不知道我们的所链接的动态静态库在哪里,但是照样可以链接成功,生成可执行程序,原因就是有相关环境变量帮助编译器进行查找。
环境变量通常具有某些特殊用途,还有在系统当中通常具有全局特性
在Linux操作系统启动时,会创建一些变量供运行时使用,这些变量就是环境变量。
比如:我们执行指令时,可以直接输入指令执行,而执行我们编译生成的可执行程序,就需要加上路径才能调用,这就是因为Linux中有一个环境变量存储了一些路径,执行指令时会默认去这些路径查找相应的指令,不需要加路径,而我们自己的程序没有在这些路径下,basn
命令行在默认的路径下找不到这个程序,所以无法执行。
这个存储了一些路径的环境变量叫做PATH
。
我们可以使用echo $name
来查看name
环境变量的内容:
可以看到,PATH
存储了多个路径,路径之间使用:
间隔。
所以如果我们将自己的可执行程序放在这些路径下,也可以直接调用,这时,我们的可执行程序就相当于是指令了;或者我们也可以将一个路径加到PATH
中,那么在该路径下的可执行程序也都会成为指令。
比如我们有如下程序:
编译后生成test.exe
,然后我们将当前路径添加到PATH
中,就可以直接调用test.exe
了。
需要注意:PATH
本质上是一个char*
指针,所以它的内容是字符串,我们可以使用PATH=echo $PATH:newpath
来为PATH
添加新路径,如果直接PATH=newpath
,那么之前的那些路径下的指令就都不能直接调用了。
除了PATH
,还有几个重要的环境变量:
USER
:当前用户
PWD
:当前路径
HOME
:指定用户的主工作目录(即用户登陆到Linux系统中时,默认的目录)
SHELL
:当前Shell,通常是/bin/bash
可以看到USER
环境变量记录了用户名,用来进行访问文件时的权限审核。HOME
记录了用户的家目录,所以不同的用户使用pwd ~
会进到不同的家目录。
创建环境变量
export
命令用来创建临时变量。
export NAME=XXX
env
命令用来显示所有环境变量:
unset
用来清除环境变量:
可以看到,TEST
环境变量被清除掉了。
环境变量的组织方式
其实每一个环境变量都是一个char*
指针,指向一个字符串内容,而且所有环境变量都被存储在一个指针数组中。
每个程序都会收到一张环境表,环境表就是那个指针数组,每个指针指向一个以’\0’结尾的环境字符串,环境表的最后一个元素为NULL
。
通过代码获取环境变量
命令行第三个参数
main
函数是有参数的,main
函数的参数就是为了接收环境变量设置的,前两个参数如下:
int main(int argc, char* argv[])
{}
argc
是int
类型的变量,而argv
是一个char*
的数组(类型为char**
的指针)。其中argv
接收的就是我们的输入及选项,而argc
接收的是argv
中元素的个数(不包含NULL
)。
现在我们在test.c
中写main
函数接收这两个参数:
然后我们执行./test
:
可以看到,argv
中存储的就是我们输入的选项。
所以在main
函数内部就可以通过argv
来执行不同的代码块,从而实现不同的效果。
这就是我们输入的指令可以携带不同选项的原理,bash
会根据argv
来决定执行动作。
除了这两个参数,main
函数还有第三个参数:
int main(int argc, char* argv[], char* env[])
第三个参数为char*
指针数组(类型为char**
的指针),用来接收环境变量表,也就是environ
表。
我们修改test.c
,让其输出所有的环境变量:(黄色高亮部分是因为没有使用到参数)
可以看到,该程序的运行结果和我们执行env
指令的结果是一致的。
所以程序中可以有两张表命令行参数表
和环境变量表
,根据这两张表,main
函数可以执行不同的动作。
getenv
如果在程序内,我们想要获取一个环境变量的值,这时通过argv
就显得非常笨重,我们需要遍历argc
数组,并比较字符串前几个字符和我们想要获取的环境变量的字符串是否相等,此时getenv()
函数就可以解决这个问题。
const char* getenv(char* name)
getenv()
函数可以根据环境变量的“名”,返回环境变量的”值“,使用该函数需要包含<stdlib.h>
。
可以看到,getenv
函数的返回值和environ
以及argv[]
中的数据是一致的。
environ
environ
是一个外部变量,在程序内部声明后才可以使用。
environ
是一个char*
数组,本身类型为char**
。
environ
指向的就是环境变量表,和main
函数接收的第三个参数env
一模一样。
可以看到运行该程序,打印结果env
中的内容一致。
环境变量表
通过上面的例子,我们可以看到:env
指令可以在bash
命令行中直接调用,并输出环境变量表,env
可以作为main
函数的第三个参数传递给main
函数,其内容和env
一致,environ
声明后可以在程序内部直接使用,其内容和env
一致。
他们三者之间是什么关系呢?
可以看到,environ
和main
函数的第三个参数的地址相同,所以它们就是同一个变量,只是名字不同。
bash
会维护一张环境变量表,指令env
就是输出这张环境变量表,而main
函数的第三个参数、extern
外部变量都是这张表的地址,它们指向的都是同一张表。
环境变量的全局属性
环境变量是可以继承的,子进程的环境变量就是父进程的环境变量,也就是说环境变量具有全局属性。
比如:我们在bash
命令行调用的进程,都是bash
的子进程,所以这些进程可以拿到bash
维护的环境变量表。
bash
本身在启动时,会从操作系统的配置文件中读取环境变量信息。
可以看到,父子进程的环境变量表是相同的。
本地变量
在bash
命令行直接创建的变量就是本地变量:
而本地变量是不会进入环境变量表的:
我们可以通过set
查看系统中的所有变量,包括环境变量和本地变量。
本地变量不会被子进程继承,只在本bash
内部有效。
本地变量的意义在于:我们需要有一些变量,不被子进程继承,只在本bash
内部有效,这些变量就是本地变量。
如果想要将环境变量变为环境变量,可以使用export NAME
来实现。
可以使用unset NAME
取消本地变量或环境变量。
命令
问题来了,既然本地变量是不会被子进程所继承的,而我们在bash
命令行运行的指令都是bash
的子进程,那为什么echo
可以知道local_virtual
的值呢?
原因是:命令行上调用的指令,不一定会创建子进程。
命令分为两种:
-
常规命令:通过创建子进程完成
-
内建命令:
bash
不创建子进程,自己亲自执行,类似于调用了自己写的或系统提供的函数。
cd
命令就是典型的内建命令。当命令行调用cd
命令时,如果创建子进程,修改的应该是子进程的路径,不会影响到父进程bash
的路径,这样cd
就失去了自己的作用,所以cd
指令要设计成内建命令。
这个程序可以改变自己的工作路径。
可以看到,这个程序成功改变了自己的所处路径(不是存储路径)。
所以bash
命令行也是同理,bash
可以对我们输入的指令进行判断,如果是cd
指令,就不创建子进程,直接调用chdir
即可。