目录
0.前言
在前面三篇博客当中,我们初步了解了什么是进程,fork创建子进程的逻辑,以及对进程状态的详解,讲解进程的优先级,本篇博客我们着重讲解环境变量。同时这也是我们Linux操作系统进程篇的第四篇博客。
本篇博客代码以及相关思维导图都已上传至Gitee码云,请君自取:
1. 引入环境变量PATH
1.1概念铺垫
Linux操作系统中的环境变量有多个,我们首先从环境变量中的PATH举例,来让大家理解环境变量,而要引入PATH,我们首先看代码的运行。
我们要执行可执行程序,导入内存中成为一个进程,就首先要找到这个可执行文件的路径,只有 路径+可执行文件名 的方式才能正确的执行一个可执行文件,这是我们现在观察到的现象。
再明确一个概念,我们编译出的可执行程序(myproc),敲的指令(ls pwd),使用的工具(gcc g++),其实本质都是在硬盘中存储的.exe可执行文件,都是可以通过 路径+可执行文件名的方式 直接导入到内存中形成进程去执行。
知道这两个概念后,我们就会产生一个疑问,那就是为什么我们执行gcc , ls这些系统级别的指令,工具(可执行文件)的时候,却不用带路径,可以直接找到这个文件,如我们ls这个可执行文件是存储在/usr/bin里面的,应该用 /usr/bin/ls来执行,但是不加寻址/usr/bin,而直接ls就可找到执行。不过我们自己写的编译出的可执行文件如myproc,a.out等,就必须带路径,如 ./myproc ,/home/zy/103/practice10/myproc ,才可以找到这个文件,只输入可执行文件名myproc,就无法执行了,command not found。
那这是为什么呢?
那肯定是因为指令,工具这些可执行文件,都是可以被系统默认找到这些文件的存储路径的!那系统是如何设置或找到这些默认路径的呢?这当然靠环境变量!!!
1.2环境变量的查看与设置(以PATH为例)
对于环境变量,我们在系统中的一个查找方式是 echo $ENV_NAME , 如对于环境变量PATH,就可以通过echo $PATH指令来查看。
我们可以看到结果是查看到PATH下有许多的路径信息,事实上,不带路径只带可执行文件名的情况下,系统能够找到一些指令,一些工具,一些可执行程序,就是从环境变量PATH下的默认路径里查找到的。
系统执行可执行程序的规则:
1.如果采用 路径+可执行文件名 的方式,则系统会在指定路径下寻找这个可执行文件去执行,如果没找到就command not found。
2.如果只输入 可执行文件名,则系统会从PATH环境变量下的一个个默认路径中依次寻找,查看每个默认路径下是否有该可执行文件。若在某个默认路径下找到了该可执行文件,则停止寻找,直接执行。
所以我们自己编译链接出的myproc可执行文件,它不在PATH下的默认路径下,在一个个默认路径之下找不到这个myproc可执行文件,所以也就出现command not found,执行失败。那我们如何让myproc像其他指令工具一样,不带路径就可以执行呢?
1.3 修改环境变量(以PTAH为例)
1.3.1 修改PATH策略选取
我们想让myproc不带路径,直接输入myproc就可以执行,就需要依靠环境变量,使得myproc可以在PATH下的默认路径中能够找到。
所以我们就有两种方法:
方法一:把myproc这个可执行文件cp拷贝到PATH下的某个默认路径之下,这样myproc在默认寻址时被找到。
方法一分析:这样其实是不推荐的,因为把一个外来的文件拷贝到一个系统级的路径执行,会污染默认路径下的命名池。
方法二:把myproc所在的路径cp拷贝到PATH下,即在PATH下添加一个新的默认路径,这样myproc在默认寻址的时候,就可以在这个新添加的默认路径中找到myproc。
方法二分析:方法二不会污染原有默认路径的命名池,是一个副作用较小的方法。
再补充一点:我们其实在安装软件到系统中的时候,整个过程其实就是把这个软件拷贝到系统变量下的默认路径,安装的过程就是拷贝的过程。
1.3.2 修改环境变量的方法(以PATH为例)
我们通常是使用export指令来添加新的环境变量,或者使用export来修改某个已有环境变量下的内容。下面我们以export修改PATH这个环境变量为例,来看一下export是如何修改PATH环境变量下的内容的。
首先明确一点:在Linux操作系统中,export对环境变量的所有修改,都是内存级别的,只是暂时的,仅本次登录有效,叉掉XShell,再登录之后,环境变量又会变回默认配置。
错误示范:
我们export修改PATH指令不能像上图那样直接给PATH赋值,不然会覆盖原来的PATH下的默认路径内容。不过不用担心,export对环境变量的修改只是内存级别的,重新登录就没事了,当然即使是内存界别,仅本次登录有效的,我们也不推荐过多的修改环境变量。
当然如果你想设置修改某个环境变量的内容为永久性的,需要你修改系统的默认配置文件,这个文件在当前用户的工作目录下,vim ~/.bash_profile,里面可以看到各种环境变量,被系统配置的默认内容。当然这个我们也强烈不推荐。
正确示范:
export PATH=$PATH:/home/zy/103/practice10/ 如图修改环境变量PATH的内容成功。
1.4 环境变量的重要性(以PATH为例)
在Windows操作系统下,也是有环境变量这个概念的,例如:
环境变量(environment variables),一般是指在操作系统中用来指定操作系统运行环境的一些参数。环境变量通常具有某些重要用途,且环境变量是具有全局性的。
环境变量一个作用体现在链接过程中,如我们在编写C/C++代码后,要使用gcc/g++对源代码编译链接,在链接的时候,其实gcc/g++编译器是不知道我们要链接的动静态库的位置在哪里,但是照样可以链接成功,形成可执行程序,其中的一个重要原因就是有相关的环境变量在帮助编译器进行查找。
2. 环境变量的分类
2.1 系统中的环境变量及查看
在系统当中,环境变量其实不止一个PATH,还有HOME,SHELL等等环境变量。比如HOME就是表明了当前用户的工作目录(/home/zy/),SHELL就表明当前使用的shell外壳,即所使用的命令行解释器(bash)。
如下图我们使用echo $指令在系统中查看这些特定环境变量的内容:
如果想要查看系统中的所有的环境变量数据,可以使用env指令进行查看。
2.2 语言级别理解环境变量
我们所定义的一个个环境变量,本质就是OS在内存/磁盘中所开辟的空间,用来存储系统配置相关的一些默认的数据信息。
还有一点不要质疑操作系统开辟空间的能力,我们在程序当中int a=0,int b=1,char c=‘A’定义变量及变量内容,本质其实都是进程在问操作系统来给进程开辟一段空间。同样的我们其实也可以类比一下,其实系统中的一个个环境变量PATH,SHELL,HOME,PWD等,其实都可以类比成一个个变量,都是OS所在内存中创造的一个个变量,都有各自的变量名以及变量内容。比如int b=1,变量b里面存储了1,PATH变量里面存储了一个个默认路径内容。
2.3 与环境变量相关的指令
1. echo:显示某个特定的环境变量的值
2. export:修改已存在的环境变量/设置添加一个新的环境变量
3. env:查看所有的环境变量
4. unset:清除环境变量
5. set:显示本地定义的shell变量和环境变量
echo $,env查看环境变量,export修改环境变量我们已经展示,set查看本地变量和环境变量&&unset清除本地变量和环境变量我们会在本地变量中展示。最后也会做一个总结。
所有的特定的某个环境变量都可以被echo $查询到,export可以修改每一个环境变量的值,或添加新的环境变量,unset可以清除任意一个环境变量。env指令仅可以查看系统级别的环境变量,而set指令可以查看本地变量。
下面了解什么是本地变量(当然本地变量也是一种环境变量,所以以上五个指令对全局的/本地变量都或多或少有效。但是全局的环境变量与本地变量之间有着本质的区别,上面各个指令也分别专职针对环境变量和本地变量,最后会总结成一个表格)。
我们这里着重讲解本地性的环境变量,即本地变量,而本篇博文主要体现的是环境变量的全局性,所以后续讲解中,对环境变量主要体现其全局性,本地变量主要体现其本地性。
3.本地变量简介
3.1本地变量的定义与查看
定义本地变量:
我们可以在Linux操作系统中直接定义本地变量。
在命令行解释器上直接定义本地变量及其内容,然后echo $就可以查看这个本地变量的内容。
查看本地变量可以使用echo $指令,同时也可以使用set指令来查看本地变量,而env指令只可以查看系统级别的环境变量,下图中使用env是查看不到定义的本地变量的,同时set不仅可以查看本地变量,也可以查看全局的环境变量。
我们删除本地变量使用unset指令来进行删除,同时unset也可以清除系统级别的环境变量。
修改本地变量我们通常直接进行修改(或使用export指令也可以对之进行修改,但有副作用)。
*PS:修改本地变量通常直接修改,如果使用export指令修改会将其修改为全局性的环境变量,所以我们通常不使用export指令来修改本地变量。
3.2本地变量的性质
先对比之前讲解的环境变量:
环境变量是一个全局变量,在全局有效,每个进程都会受其影响,全局只有一份。
环境变量虽然通过export修改后是内存级别的,暂时性有效,但是通过设置达到永久有效。
环境变量本质上是OS在磁盘/内存中开辟的一段空间,存储相应环境变量及其内容。
环境变量是一种系统级别的变量。
事实上,系统不仅允许你创造一些系统级别(全局)的变量,同时也允许你创造一些本地变量。比如我们之前看到的PATH,HOME,SHELL,是OS创造的环境变量,比如我们定义的myval1,myval2,这些是我们定义的本地变量。
本地变量的性质:
1. 本地变量是局部变量,只在局部有效,并不能影响全局的进程。
2. 本地变量仅在本次登录有效,下次登录时便“销声匿迹”。
3.3 指令使用规范总结
echo $XXX | 既可以查看全局环境变量的内容,也可以查看本地变量的值。 |
env | 只可以查看系统级别的全局环境变量。 |
set | 既可以查看本地变量,又可以查看全局环境变量。 通常set专门查看本地变量,env专门查看全局环境变量。 |
export | 可以修改已有环境变量的内容,或添加新的环境变量。 注意不要使用export给本地变量改值,不然会修改本地变量为全局的环境变量,本地变量可以直接修改。 |
unset | 可以清除本地变量,也可以清除全局环境变量。 |
4. 在程序中获取环境变量
4.1 命令行参数讲解
4.1.1 理论讲解与基本演示
在我们平时书写程序是,定义的 int main()函数通常是不携带参数的。但是事实上,main函数是可以携带参数的!main函数可以被传参,可以带参数项!
如图我们展现一下main函数通常携带的两个参数:int argc与char* argv[]。
如果你什么都不带,那只会传进去 ./myproc 这个字符串(指针)。所有的字符串,都会无一例外被传入!! 以空格作为分隔符,都被看作字符串传入到进程当中。所以我们有一个字符串指针数组结构的argv参数,里面存储的就是传入的字符串参数,里面有argc个有效字符串(指针)。
这个字符指针数组argv[]里面有argc个有效元素,argv指针数组最后应该元素指向空NULL。
4.1.2 argc参数的必要性
因为最后这个传入的字符串指针数组char* argv[],最后一个元素指向的是NULL,所以for(int i=0;i<argc;++i)其实也就不用了,我们直接for(int i=0;argv[i]!=NULL;++i)也行的,那既然最后一个是NULL, 那代表传入有效参数个数int argc不要不也行吗?
当然argc也是有用的, argv[]这里面一个一个的有效字符串参数,是用户传入的,个数是argc个,如果没有统计好这些个数,需要我们自己遍历统计,影响操作。
而且我们这个可执行程序,需要限制你传入参数的个数,可以直接通过argc知道你传入的字符串参数的个数,比如就可以立即知道:你传入了argc个字符串,你传入了 argc-1 个选项。例如你必须让传入5个字符串参数(必须传入4个选项):
所以argc作用大大滴,我们需要argc代表传入有效参数的个数。
4.1.3 命令行参数简单应用示例
我们的ls,其实是指令其实是可以根据传入不同的参数选项,执行不同的逻辑,如 ls -a -l ,
如 ls -n 等,这其实也是通过程序main函数的传参达成的,下面我们通过一个例子模拟一下这个过程。
#include<stdio.h>
#include<unistd.h>
#include<string.h>
void Usage1(){
printf("Usage: Only two parameter like 'myproc -a'\n");
}
void Usage2(){
printf("Usage: Only can choose one of -a -b -h as parameter\n");
}
int main(int argc,char* argv[]){
if(argc!=2){
Usage1();
return 1;
}
for(int i=0;i<argc;++i){
if(strcmp(argv[1],"-a")){
printf("hello world!\n");
}
else if(strcmp(argv[1],"-b")){
printf("hello CUGers!\n");
}
if(strcmp(argv[1],"-h")){
printf("Everyone can get the goal!\n");
}
else{
Usage2();
return 2;
}
}
return 0;
}
这里的 myproc 就像 ls。ls,myproc所带的选项的不同都可以执行不同的功能。如 ls 带 -l ,-a ,-n等选项,myproc带 -a ,-h ,-b等选项。
指令有很多选项,用来完成同一个命令的不同子功能,选项底层使用的就是我们的命令行参数!!!
现在其实你也可以编译形成一个sort可执行程序 , -q代表快排 ,-b代表冒泡……使用选项代表不同的功能。
4.2 环境变量参数对进程的传入
讲完命令行参数(int argc)char* argv[]的使用之后,我们其实在给main函数传参的时候,main函数也也可以带一个字符串指针数组char* env[], 这其实命令行参数中除argc argv以外的第三个参数!
补充:父进程在fork创建子进程的时候,父进程的环境变量也都会传给子进程。在子进程中,子进程在执行它main函数时我们可以通过char* env[]这个参数来获取到父进程创给子进程的环境变量。
4.3 使用全局变量environ获取
刚才我们是通过main函数参数在程序中获取该进程被传入的环境变量,当然我们还可以通过第三方变量environ来在程序中获取全局环境变量。
这个environ的类型是一个二级指针char**,事实上,environ所指向的元素是一个个char*的字符串指针,char** environ的结构从某种程度上来说和char* env[]是非常像的。
4.4 使用函数接口获取
使用 getenv 接口,就可以在进程当中,获取某个特定的环境变量。
刚刚的两种方式 char* env[] main函数接收参数 OR environ全局变量获取
其实都不常用!我们在程序里面获取环境变量用的其实是getenv函数!!
5. 环境变量的全局性
5.1 引入概念与铺垫
我们刚才理解了环境变量的概念,环境变量的数据组织结构,系统中对环境变量、本地变量的区别与指令操作,以及程序中对环境变量的三种获取方式,接下来我们来着重讲解环境变量的全局属性vs本地变量的局部性。
环境变量是具有全局属性,我们可以在全局,任何一个进程当中都能获取到系统中同一份环境变量。那环境变量是如何被每一个进程获取到的呢?
我们首先看一个程序:
所以我们可以得出一个结论:
命令行上启动的进程,其父进程都是bash !!!!!!这里其实就是bash创建了你这个(子)进程myproc。
5.2 环境变量全局性的体现
子进程的环境变量从哪里来?那当然都是父进程给子进程传入的,父进程在创建子进程的时候,父进程会给子进程传入父进程中的环境变量内容。
其实我们在之前程序中,使用进程的main函数传入的char* env[]参数其实就是父进程传给子进程的全局环境变量。如果main函数不接收这个参数,并不代表子进程就不会接收到父进程的环境变量,事实上父进程也会给子进程传入环境变量,我们可以在子进程的参数列表中看到。
bash(命令行解释器)也是一个进程,而通过5.1我们知道:命令行解释器上运行起来的所有进程,其父进程都是bash,所以这些子进程都会被父进程bash传入环境变量。然后这些子进程就都有了系统级别的环境变量。
然后这些在命令行bash中创建出来的子进程,自己作为新的父进程,再fork()创建出的子进程,就会再把这一份从bash获得的全局环境变量传承给他们的子进程。
从这个意义上说,bash的那一份环境变量,会被无限的一辈一辈的往下传承到其子,孙,重孙子进程....,所以其实系统中所有的进程都可以使用到这同一份环境变量,这其实就是系统级别的环境变量的全局性!!!
那bash的那一份环境变量是从哪里来的呢?
bash的环境变量一般是系统给的,也就是说bash(命令行解释器)在登录创建的时候,会从系统的配置文件中读取环境变量,将全局环境变量导入到自己进程的上下文当中。因为环境变量本身其实就是OS操作系统在系统中开辟的一段空间,并写入存储了基本的环境变量数据。
所以在各个进程当中,我们能够使用char* env[]查到这些环境变量的内容。其实都是shell(Linux下叫bash)从系统的配置文件中读取导入到自己进程的上下文当中,然后再由shell(bash)将自己从系统中读取的环境变量,传给从bash上创建的一个个子进程。子进程在创建它的子进程的时候,也会将这同一份环境变量,传给子进程的子进程。
所以事实上,我们 ls , pwd , gcc , g++ 这些可以直接输入名字即可执行的指令/工具,他们在在命令行上执行起来时都是一个系统中的进程,这些进程其实都是bash(命令行解释器)的子进程,所以这些指令/工具的进程都会被bash传入全局环境变量,所以这些进程可以使用PATH环境变量,也就是说ls/pwd/gcc/g++/...(指令/工具==可执行文件 -> 进程)可以被
5.3 本地变量的局部性
5.3.1 验证本地变量局部性
刚才我们知道,环境变量具有全局性,全局性的环境变量是可以被父进程传给子进程的。同时我们知道,本地变量是具有局部性的,那本地变量可以被bash(父进程)传给子进程吗?
说明是本地变量没法继承下来的,即父进程不会把本地变量传给子进程,所以我们说环境变量通常是全局的,本地变量是局部的。
那如何让本地变量mylocalval可以像全局环境变量一样被父进程传给子进程呢?
5.3.2 export修改变量
我们知道,export指令的作用,一是可以对已有的全局环境变量进行修改,二是可以设置添加新的全局环境变量。但是如果我们使用export修改本地变量的值,那本地变量的值不仅会被修改,本地变量此时也不再是本地变量,其属性会变为全局性的环境变量,那这样其实mylocalval就可以作为全局的环境变量传给子进程了。
PS:不要拿export指令随意修改本地变量的值,本地变量的值可以直接在命令行进行修改。
6. 简单总结
至此我们对环境变量介绍完毕,在再总结一下,基本概念阶段of环境变量:环境变量就是一堆数据。而且不要质疑OS开辟空间的能力,你在程序中所用的空间基本都是OS给你开辟的,OS当然也可以给自己开辟空间保存系统数据。所以环境变量其实就是OS在磁盘/内存中创建定义的一批数据。这批数据可以被bash来读取到,所以环境变量也被bash读取到,环境变量也被传给子进程中,所以环境变量具有全局性。所以在编译链接的时候,这些子进程就知道了库在哪里,本质上就是环境变量就可以指导相关工具进行查找,下面我们在讲链接的博客中,会细讲。
本博客的内容大体可以用如下思维导图来表现(已放至文章开头Gitee中,君可自取):