基础
环境变量一般是操作系统中用来指定操作系统运行环境的一些参数,在系统中具有全局特性,还有某些特殊用途。
我们先来认识一个环境变量PATH,通过它我们就能大致了解环境变量的概念与特性。我们知道,我们输入的命令都是先由命令行解释器bash处理的,绝大多数指令都有对应的可执行文件,执行这些指令的本质就是让bash通过路径找到文件并运行。为什么执行我们的文件需要加上路径,比如 ./test 或 /home/uesr1/test 等,而执行指令却不用呢?事实上,如果我们能知道 ls 的可执行文件放在哪,我们也可以用这种路径访问的方式运行 ls ,它就在路径 /usr/bin 下。使用/usr/bin/ls 也能执行ls指令,各种选项也可以正常使用。那么bash是如何找到 ls 的可执行文件在哪的呢?答案是通过环境变量PATH。如果想查看环境变量的具体内容,可以使用echo $[环境变量]
我们可以看到里面有多个路径,如果bash在这些路径下都没有找到要运行的指令,就会发出我们熟悉的报错。
PATH也是可以修改的,使用命令 PATH=$PATH:[路径] 就能在其末尾添加路径,这样就可以直接运行对应路径下的文件。不过通过这种方式作出的修改在重新登录后就会重置。或者我们也可以反过来把代码的可执行文件拷贝到这些路径下,这样就能像指令一样不用 ./ 直接运行,需要root权限,但是不建议这么做。
环境变量本质就是一些字符串,字符串里面是一些bash经常需要用到的信息,比如家目录、用户名、配色方案、当前工作路径等。bash创建时会从系统的相关配置文件中读取环境变量,通过申请内存空间来存储这些环境变量,用指针数组(也是向内存申请来的空间)存储指向各个空间的指针。这个指针数组就是环境变量表,bash就是通过它来使用各个环境变量。 在家目录下使用ls -al 或者直接 ll 就可以查到配置文件,下面的 .bashrc 就是配置文件,不同发行版本的Linux系统可能有所不同。
在配置文件.bashrc中修改PATH就可以实现永久更改,即使重新登录也不会被重置。直接在文件末尾添加命令 export PATH=$PATH:[路径] 即可,如下。
接下来我们继续认识其它一些常见的环境变量,直接输入指令env就可以查看所有的环境变量,下面是常见环境变量的解释。不同版本的Linux可能有所差异。
HOME 家目录
USER 当前用户的用户名
LOGNAME 登录的用户的用户名,一般和USER相同
HISTSIZE 记录历史命令条数的上限,1000就是最多记录之前的1000条命令
HOSTNAME 当前主机的主机名
LS_COLORS 配色方案
LANG 编码格式
PWD 当前工作路径
SSH_TTY 是当前终端设备名
OLDPWD 是上一次所在的路径,使用 cd - 返回上一次所在的路径就是通过它实现的。
修改已有的环境变量很简单,使用前面提到过的命令 [环境变量]=$[内容] 即可。我们也可以新定义环境变量或取消已有的环境变量。
export [变量名]=[内容] 新增环境变量
unset [变量名] 取消环境变量
在程序中也可以查看环境变量,事实上,进程地址空间中有一个区域叫做环境变量块,是栈和堆之间的共享区的一部分,里面存储的就是该进程的环境变量。进程的PCB中自然也有指向这个区域的指针。
由此可见,包括bash在内的每个进程都有自己的环境变量,初始时都是继承自父进程,我们之后在程序中查看的环境变量全都来自这个环境变量块。比较特殊的是bash为了提升效率,自己还另外维护了一张环境变量表,其次,bash的环境变量除了继承自父进程,还会从配置文件中读取。
命令行参数
不过在正式开始用代码查看环境变量之前,我们需要补充一下命令行参数的知识,顺便了解bash中的另一张表——命令行参数表。我们知道C语言的main函数可以接收两个参数:
int main(int argc,char* argv[])其中argv就是用于接收各个命令行参数的,它以NULL结尾。当我们在使用命令./code ac b 运行可执行文件code时,这一行命令会被bash按照空格划分成三段字符串,分别是./code 、ac和b,然后先传到bash中的命令行参数表,再传给main函数的参数argv,这些字符串就是命令行参数。argc则是命令行参数的个数,也就是argv中有效元素的个数。通过这两个参数,可以实现程序的不同子功能。我们用各种选项使用指令的不同功能就是这样实现的,通过参数argv来查看附带了什么选项,从而运行对应的功能。
#include<stdio.h> #include<unistd.h> int main(int argc,char* argv[]) { if(argc==1) printf("默认功能已启用\n"); else for(int i=1;i<argc;i++){ printf("功能 %s 已启用\n",argv[i]); } return 0; }不难看出,bash中的命令行参数表就是用于临时存储输入的命令行参数。那么为什么命令行参数要先传给bash呢?不要忘记了,我们只是输入了命令,真正去运行可执行文件的是bash。只有将文件运行起来才会调用main函数。需要先让bash通过第一个命令行参数(./code)找到相应的可执行文件并运行,才能将所有的命令行参数传给argv。所以命令行参数自然是要先交给bash。上面的命令中是直接给出了可执行文件code的路径,如果是像 ls 之类的指令,bash则会通过环境变量表中的PATH寻找对应的可执行文件。
获取环境变量
接下来我们来看程序中如何获取环境变量。使用main函数的第三个参数char* envp[]可以查看继承自父进程bash的环境变量,和argv一样以NULL结尾。
#include<stdio.h> #include<unistd.h> int main(int argc,char* argv[],char* envp[]) { (void)argc; (void)argv; int i=0; char* cur=envp[0]; while(cur){ printf("%s\n",cur); i++; cur=envp[i]; } return 0; }这里提一点,编译器会自动识别main()函数使用了几个参数,从而确定在调用main函数时要传入几个参数。如果后面没有使用这些参数可能会警告甚至报错。可以将不使用的参数强转成void类型,通过这种特殊的使用方式来避免报错。下面是部分运行结果。
也可以使用函数char* getenv(const char* name) 根据传入的环境变量名获取环境变量的内容,需要头文件stdlib.h。getenv获取的是当前进程的环境变量。通过获取环境变量USER还可以让代码只有特定用户能正常运行,如下。
#include<stdio.h> #include<stdlib.h> #include<string.h> int main() { const char* user=getenv("USER"); if(user==NULL) return 1; else if(strcmp(user,"tester")==0) printf("猿神,启动!!!\n"); else printf("你无权使用\n"); return 0; }最后还可以使用头文件unistd.h中的全局变量environ,它是以NULL结尾的指针数组,数组中的指针指向的就是环境变量块中的各个环境变量,通过它可以查看当前进程全部的环境变量。前面的getenv就是用environ查找的环境变量。由于environ是外部文件的变量,需要先用extern声明后才能使用。
#include<stdio.h> #include<unistd.h> extern char** environ; int main(){ int i=0; char* cur=environ[0]; while(cur){ printf("%s\n",cur); i++; cur=environ[i]; } return 0; }在程序中也可以为当前进程添加环境变量,调用putenv函数即可,需要头文件stdlib.h。定义如下。
int putenv(char *string);如果添加的环境变量已经存在,则会修改该环境变量的内容。putenv新增环境变量时会在environ中添加指向新环境变量的指针,但不会在main函数的参数envp中添加,毕竟envp只是个局部变量,甚至都不一定存在。所以envp只能查看继承自父进程的部分,新增的环境变量查看不了。
putenv的具体使用如下,定义环境变量的字符串不能被销毁,最好定义成全局的。
#include<stdio.h> #include<stdlib.h> char* temp="my_new_key=confidence"; int main(){ const char* en="my_new_key"; putenv(temp); printf("my new key is %s\n",getenv(en)); return 0; }不过在程序中通过putenv添加的环境变量只有该进程及其子进程能使用(所有运行中的程序都是进程),它的父进程是用不了的,包括bash。
在命令行中还可以用一种特殊的方式添加环境变量,那就是将已有的本地变量转化为环境变量。在命令行中直接定义的变量就是本地变量,比如count=1、my_key=patience等。本地变量主要的用途就是临时数据的存储与循环的控制,如下
它不会被子进程继承,只在bash内使用。使用set就可以查看所有变量,包括环境变量和本地变量。
前面使用的指令export就可以把本地变量改成环境变量。但我们知道:进程之间具有独立性,进程之间不能相互修改数据,export应该是不能修改环境变量的,因为环境变量是进程bash中的数据,那它是如何做到的呢?其实export是内建命令,执行内建命令时不需要创建子进程,而是由bash亲自执行。常见的pwd和cd也是内建命令。执行这些命令是由bash自己调用函数或系统调用接口。如下,将本地变量改为环境变量。