二、环境变量
环境变量一般是指操作系统中用来指定操作系统运行环境的一些参数,比如我们在编写c/c++代码的时候,在链接的时候,从来不知道我们的所链接的动态静态库在哪里,但是照样可以链接成功,生成可执行程序,原因就是有相关环境变量帮助编译器进行查找,环境变量通常具有某些特殊用途,还有在系统当中通常具有全局特性。比如我们在linux中写的可执行程序,要想这个程序先运行起来必须在前面就是 . / , . 是在当前路径,/ 是路径分隔符,而同样为可执行程序的 ls指令等却不需要在前面加上./,难道就因为一个是被纳入系统的程序一个是我们自己写的吗?其实并不是这样,系统的指令之所以不需要在前面加./是因为有环境变量的帮助,这个环境变量会帮我们去搜索系统中的ls命令,而这个环境变量叫PATH,下面我们查看一下这个环境变量:
echo是打印一个字符串,PATH是环境变量,前面加上$符号就是获取环境变量的内容,这里与指针解引用相似。
和环境变量相关的命令:
1.echo:显示某个环境变量值
2.export:设置一个新的环境变量
3.env:显示所有环境变量
4.unset:清除环境变量
5.set:显示本地定义的shell变量和环境变量
我们之前讲过linux的指令,which可以查看指令的路径,通过查看我们发现ls指令在usr/bin中,而PATH环境变量是根据冒号一个一个路径进行查找,当找到usr/bin这个路径的时候就不需要我们在使用的时候加./了。那么如何将我们写的可执行程序添加到环境变量中呢?看下图:
我们写了一个程序用来演示。接下来我们用命令将我们写的这个程序加入到环境变量中
可以看到我们成功添加,然后我们试试可以直接运行吗?
我们看到是可以运行的并且不用在输入前面的./了
这个时候我们的其他指令不能使用该怎么办呢?这时只需要重新登录xshell即可。
那么我们怎么样才能既使用系统的指令又用自己的呢?
我们将刚刚的命令修改一下就可以既使用系统的指令又用自己的。当然除了这一种方式我们还可以直接将要添加的可执行程序的目录拷贝到PATH中,而这种方式在linux中相当于软件的安装。
下面我们用env指令查看系统中的环境变量:
我们可以看到系统中的环境变量很多,当然我们也可以用history指令查看以往我们用过的指令:
为什么从10开始呢,因为history只会保留最新的1000条指令,一旦超过就会删掉原来旧的指令。
下面我们用C语言来获取系统中的环境变量:
我们再写c/c++的时候从来没有写过main函数的参数,而main函数实际上有3个参数,这三个参数不需要我们手动去写编译器会默认给我们传参,envp这个指针数组中每一个指针都指向一个有效的字符串,而最后一个指针必须以NULL结尾。
然后我们将代码写完整如下图:
为什么for循环中envp没有写判断呢?因为我们刚刚说过,envp这个指针数组中最后一个指针指向的一定是NULL,而NULL在for循环中对应为假,所以不需要写判断语句。
由于使用main函数的三个参数是c99标准下的,所以我们在编译后面加上c99。
然后我们直接运行程序发现这里的环境变量与我们用env命令显示的一致。
总结:环境变量本质就是内存级的一张表,这张表由用户在登录系统的时候,进行给特定用户形成属于自己的环境变量表。环境变量中的每一个都有自己的用途,有的是进行路径查找的,有的是进行身份认证的,有的是进行动态库查找的,有的是用来进行确认当前路径等等,每一个环境变量都有自己特定的应用场景。那么环境变量对应的数据都是从哪来的呢?是从系统的相关配置文件中读取进来的。下面我们验证一下:
我们先用ctrl + ~进入家目录,然后输入指令ls -al查找文件:
我们可以看到bash的两个shell脚本,然后我们用vim打开这个脚本:
我们用vim进入etc/bashrc,注意在etc目录下bashrc是全局的。
比如我们命令行上的# 或者 $提示符就是这样编写的。 环境变量是通常具有全局属性的,当我们写了一个环境变量val=100,然后这个环境变量就会shell的表中,当我们给这个进程在开一个子进程的时候,shell中的这张表也会交给子进程,这样子进程中也就有了环境变量val=100,下面我们来证明一下:
我们先自己导入一个环境变量,然后输入env命令查看:
我们看到确实将这个环境变量添加到系统中,下面我们修改一下代码进行演示:
getenv()函数是获取一个环境变量并打印
我们从上图中可以看到成功获取到了我们自己设的环境变量,我们前面讲过当我们运行一个进程的时候这个进程的父进程是bash,刚刚我们的环境变量是保存在系统中也就是说只有bash可以访问,但是现在这个子进程也可以使用就说明了环境变量是全局的,会由父进程传给子进程。
我们定义了一个变量前面没有加export,然后我们在前面加$符号打印其内容发现也能正常打印
下面我们用getenv获取一下这个环境变量:
这个时候我们发现用函数获取环境变量获取不到我们刚刚定义的hello1,也就是说不带export定义的环境变量是不可以被getenv()获取到的,那么也就不可以被子进程继承,那么为什么加了export就能被继承呢?因为不加export定义的环境变量是不会被添加到环境变量表中,这样的变量被称为shell的本地变量,这种变量只能在shell内部有效。
因为hello1已经在shell本地了,所以前面加export可以直接将hello1添加到环境变量表,也就可以正常被子进程继承了。
接下来我们继续解释刚刚main函数的三个参数中的另外两个。
argv的使用与argc都有一个共同点,就是不需要在判断结束条件。
如下图所示:
-a -b其实是参数选项,my2.24是可执行。那么这个操作有什么作用呢?
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> //int main() //{ // int cnt = 10; // while (cnt--) // { // printf("这里在倒计时:%d\n",cnt); // } // return 0; //} void Usage(const char* name) { printf("\nUsage:%s -[a|b|c]\n",name); exit(0); } int main(int argc,char* argv[]) { if (argc!=2) Usage(argv[0]); if (strcmp(argv[1],"-a")==0) printf("打印当前目录下的文件名\n"); else if(strcmp(argv[1],"-b")==0) printf("打印当前目录下的详细信息\n"); else if(strcmp(argv[1],"-c")==0) printf("打印当前目录下的文件名(包含隐藏文件)\n"); else printf("其他功能,待开发\n"); //{ // for (int i = 0;argv[i];i++) // { // printf("argv[%d]->%s\n",i,argv[i]); // } return 0; } //int main() //{ // printf("myenv:%s\n",getenv("hello1")); // return 0; //}
我们重新写一段代码,然后我们运行起来。
启动软件后这个软件告诉我们使用方法是./my2.24 + abc任意一个字符
这样就能完成一个类似于打印目录的操作,这只是简单的演示实际上可以实现一些有用的东西。
总结
本篇文章相较于上一篇进程的概念多了很多需要实践的东西,比如测试进程的优先级,理解孤儿进程,学会理解环境变量并且可以自己添加环境变量,环境变量的获取,环境变量的修改等。下一篇继续更深入的学习linux的进程,即使进程这部分概念多也希望大家可以多多练习才能更深入的理解进程。