环境变量概念
环境变量
一般是指在操作系统中用来指定操作系统运行环境的一些参数
在编程中,我们可以定义变量,而定义变量的本质,就是在内存开辟一块空间。
在程序运行的过程中,也可以定义变量,而Linux操作系统本身就是一个用C语言写的程序,因此操作系统可以在运行的过程中开辟空间。环境变量的本质,就是操作系统运行过程中,为自己开辟的空间,存储了一些重要的信息。
现在来思考一个问题,我们在执行指令的时候,可以直接输入指令名,但是我们自己的可执行程序,一定要加上绝对路径或者相对路径,这是为什么?
PATH
:一个环境变量,存储着多个路径,在这些路径下面的可执行程序,可以直接执行
PATH
是我们讲解的第一个重要环境变量,我们现在尝试观察这个环境变量:
echo $xxx
:查看xxx
环境变量的内容
现在我们执行指令echo $PATH
来查看PATH
环境变量的内容:
可以看出,其是由多个路径组成的,每两个路径之间由一个冒号:
隔开。
当在命令行输入指令时,操作系统会默认到这些路径下去查找,如果都没有找到,就会报错。
因此我们只需要把自己的可执行程序放到这里面的某一个路径下面,我们自己写的可执行程序也可以当作指令来执行了。而这个工作,就是把自己写的软件安装到系统中。
我们也可以想办法把自己的路径加到PATH
环境变量中。
xxx=
:修改xxx
环境变量的值
现在当前目录下有一个可执行程序test.exe
,使用./test.exe
后输出hello world
:
该可执行程序处于路径/home/box-he/CSDN/process/env
下,我们将这个路径加到PATH
中,执行指令PATH=/home/box-he/CSDN/process/env
:
现在我们就可以直接执行指令test.exe
了,但是由于PATH
的其它路径都被这个路径覆盖了,我们原有的ls
,pwd
,mkdir
等等指令,都执行不了了。
但是别慌,此时我们只需要关闭xshell
,然后重启,此时PATH
就会恢复原先的值。至于为什么,我们后面再详细讲解。
我们再来看几个重要的环境变量:
USER
:记录当前的用户
PWD
:记录当前路径
HOME
:记录家目录
在当前环境下,它们的值如下:
我们能否自己定义环境变量呢?
export xxx=
:定义xxx
环境变量
比如我定义一个环境变量Linux
,其内容为Linux Is Not unix
:
此时echo $Linux
就可以输出目标语句了,不过自己定义的环境变量,在xshell
重启的时候,也会失效。
最后要讲解的一个环境变量基础操作,是env
指令:
env
:输出所有环境变量
使用指令env
:
窗口大小限制,这里只列举了一部分环境变量,但是我们可以看到几个熟悉的环境变量,比如PWD
,PATH
等。
查看环境变量
我们先前说过,可以通过env
来查看所有环境变量,也可以通过echo $xxx
来查看单个环境变量,但是这些都是在命令行中操作的,在一个可执行程序中,要如何查看环境变量呢?
getenv
getenv
是一个函数,其定义在<stdlib.h>
中,功能是:输入一个字符串作为参数,该函数输出该字符串对应的环境变量。
当前test.exe
执行如下代码:
#include <stdio.h> #include <stdlib.h> int main() { const char* path = getenv("PATH"); const char* home = getenv("HOME"); printf("PATH = %s\n", path); printf("HOME = %s\n", home); return 0; }
输出结果:
可以看到,getenv
函数确实可以取出单个环境变量对应的值,这个方式也是最常用的。
environ
environ是一个外部的变量,只需要extern后就可以直接使用,本质是一个char*的数组,也就是说其类型为char* []或者char**。
environ的每个元素都是char*指针,指向一个字符串,字符串内部存储的就是环境变量。而environ的最后一个元素是NULL,用于帮助程序员判断什么时候数组到了结尾。
接下来我们在test.exe
中执行如下代码,看看environ
里面的字符串:
#include <stdio.h> int main() { extern char** environ; for(int i = 0; environ[i] != NULL; i++) { printf("environ[%d] = %s\n", i, environ[i]); } return 0; }
输出结果:
可以发现,每个元素确实都是指向字符串的char*
指针,输出后每个元素都对应一个环境变量。
main的参数
也许你听说过,main
函数也是有参数的,但是在C/C++
学习中,这个参数好像可写可不写,学习环境变量后,我们就可以了解一下这些变量的意义是什么了。
argc & argv
main
的前两参数分别是argc
和argv
,传参形式如下:
int main(int argc, char* argv[]) {}
可见,argc
是int
类型的变量,而argv
是一个char*
的数组。还记得我们刚刚的environ
也是一个char*
的数组吗?其实它们两个的结构是一样的。
argv
每个元素都是char*
类型,分别指向一个字符串,argv
的最后一个元素也是NULL
,用于标识argv
的数组结尾。
而argc
代表了argv
中元素的个数,所以我们既可以通过NULL
来判断argv
结尾,也可以通过argc
来判断结尾。
在test.exe
中执行如下代码,来看看argv
中存储了什么:
#include <stdio.h> int main(int argc, char* argv[]) { for(int i = 0; i < argc; i++) { printf("argv[%d]: %s\n", i, argv[i]); } return 0; }
直接执行./test.exe
:
现在argv
中只有一个元素,即字符串"./test.exe"
。
给./test.exe
加几个选项试试,./test.exe -a -b -c
:
此时argv
多了三个元素,分别是字符串”-a“
,"-b"
,"-c"
,也就是说,argv
内部存储的是我们执行可执行程序的时候,输入的所有选项?
在随便输入些东西试试,执行./test.exe -std=c99 -std=c++11
:
现在字符串又变成了"-std=c99"
和"-std=c++11"
了,看来不是巧合。
因此可以得出结论:argv
参数内部,存储的是调用可执行程序时,输入的选项。
那么argv
有什么意义呢?
我们在test.exe
中执行以下代码:
#include <stdio.h> #include <string.h> #include <stdbool.h> int main(int argc, char* argv[]) { bool flagA = false, flagB = false, flagC = false; for(int i = 0 ; i < argc; i++) { if (strcmp(argv[i], "-a") == 0) flagA = true;//说明输入了-a选项 else if (strcmp(argv[i], "-b") == 0) flagB = true;//说明输入了-b选项 else if (strcmp(argv[i], "-c") == 0) flagC = true;//说明输入了-c选项 } printf("正在执行 test.exe\n"); if(flagA) printf("功能a执行中...\n"); if(flagB) printf("功能b执行中...\n"); if(flagC) printf("功能c执行中...\n"); return 0; }
此时程序就可以根据我们输入的选项,来判断要执行哪些功能:
我们使用的绝大多数指令,都是有大量选项的,我们要通过输入不同的选项,让程序执行不同的功能,而程序就是通过识别argv
,来判断用户输入了哪些选项,进而执行特定功能的。
在我们向bash
输入一大段指令的时候,指令本质就是一个字符串,bash
会把字符串拆解为一个个小的字符串,然后把它们整合到一个叫做命令行参数表
的东西中,命令行参数表
其实就是一个指针数组char* []
,而argv
参数就可以接收这个bash
维护的数组,在程序内部读取。
env
main
的第三个参数叫做env
,其也是一个char*
的指针数组,类型为char* []
或者char**
。
int main(int argc, char* argv[], char* env[]) {}
到目前为止,相信你已经对char* []
这个类型的变量比较敏感了,没错,他也是一个字符串指针数组,并且最后一个元素为NULL
。
其和各个讲的environ
变量是一模一样的,内部存储了环境变量,以及环境变量的值,我们向相同的方式来输出这个env
:
#include <stdio.h> int main(int argc, char* argv[], char* env[]) { for(int i = 0; env[i] != NULL; i++) { printf("env[%d]: %s\n", i, env[i]); } return 0; }
输出结果:
我们又看到了这张熟悉的环境变量表,甚至我们可以发现:指令env
,外部变量envrion
,main
的第三个参数env
,它们输出的结果都是一模一样的!
bash
会维护一张环境变量表,存储着各种环境变量,而指令env
,还有外部类变量envrion
,以及main
的第三个参数env
,它们的环境变量表都是这个bash
维护的环境变量表
。
我们写一个程序验证一下:
#include <stdio.h> int main(int argc, char* argv[], char* env[]) { extern char** environ; printf("%p\n", &environ[0]); printf("%p\n", &env[0]); return 0; }
我们取出了main
的第三个参数env
的首元素地址,以及外部变量envrion
的首元素地址。
输出结果:
可以看到,两者地址是一样的,说明它们就是同一张表,或者说是同一个数组。即bash
维护的环境变量表,而我们echo $xxx
,其实本质上也是去这个环境变量表中查找对应的环境变量,并取出值来。
bash的环境变量
其实环境变量是可以继承的,我用一段代码证明:
#include <stdio.h> #include <unistd.h> #include <sys/types.h> int main(int argc, char* argv[], char* env[]) { pid_t id = fork(); if(id == 0)//子进程 { for(int i = 0; i < 3; i++) { printf("child: env[%d]: %s\n", i, env[i]); } } else//父进程 { for(int i = 0; i < 3; i++) { printf("father: env[%d]: %s\n", i, env[i]); } } return 0; }
通过fork
创建了一个子进程,然后父子进程分别输出env
这个数组的前三个字符串。
输出结果:
可以看到,父子进程都可以正常使用env
,说明子进程是可以继承父进程的环境变量表
的。
而所有在命令行调用的进程,都是bash
的子进程,因此我们的在命令行调用的进程可以继承到bash
的环境变量表!
只有我们登录了系统的时候,bash
才会被创建,先前我们知道,bash
要维护一张环境变量表。那么bash
的环境变量是怎么来的呢?
其实这些环境变量是被存在磁盘中的,而我们启动bash的时候,会把这些环境变量从磁盘中拷贝到内存中,组成一个环境变量表。我们访问环境变量,都是在访问内存中的环境变量。我们修改环境变量,也是在修改内存中的环境变量。
而当我们重启shell,那么bash就会重新去磁盘拷贝一份环境变量,我们之前对环境变量的所有修改,都没有影响磁盘中的环境变量,因此我们重启的时候,可以重置环境变量。
那么环境变量存储在磁盘的哪里呢?
在家目录中,有一个叫做.bash_profile
的隐藏文件,其内部存储的就是环境变量。
其内容如下:
我们可以看到PATH
环境变量就在里面,给出了另外一个文件~/.bashrc
,存储的只是一小部分环境变量,剩下的要去~/.bashrc
看。
而其实~/.bashrc
内部还链接了其它文件,这个环境变量的文件一层套一层,比较复杂,如果感兴趣,可以去自己探索一下。