目录
前言:
命令行参数
环境变量
直接看现象
更多的环境变量
尝试理解环境变量
前言:
今天介绍的是一个较为陌生的名词,环境变量,在学习环境变量之前,我们需要一定的预备知识,这个预备知识是命令行参数,所以今天我们介绍环境变量的时候,介绍顺序是先介绍命令行参数,然后直接看有关环境变量的现象,到更多的环境变量,最后我们尝试来理解环境变量。
现在进入第一个话题,预备知识——命令行参数。
命令行参数
介绍命令行参数我们从main函数开始,相信不少人是比较疑惑的,这和C语言的main函数有什么关系?还真有,只不过是我们最开始忽略了main函数的参数而已,相信这个时候有人开始有反应了,main函数我们学习的时候确实会有人带参数,只是因为没有方法传参进行调用,也就不了了之了:
5 int main(int argc,char* argv[]) 6 { 7 8 9 return 0; 10 }
今天的命令行参数就从argc argv开始。
那么先来一段代码:
5 int main(int argc,char* argv[]) 6 { 7 for(int i = 0;i < argc;i++) 8 { 9 printf("%s\n",argv[i]); 10 } 11 12 return 0; 13 }
为什么写这样一段代码呢?我们不妨对参数进行一点分析,int argc说明argc是一个整形,后面的argc是一个char*类型的数组,就代表说这个数组的内容是char*,那么我们应该是可以尝试进行打印的,所以我们写了如下代码,make一下之后,我们看看结果是什么:
结果是我们在命令行参数不管输入多少都会进行打印,并且打印顺序默认是从左到右,即最开始的一定是./test,那么这是为什么呢?
我们再来看一段代码:
6 int g_val = 100; 7 int main() 8 { 9 int a = 1; 11 pid_t id = fork(); 12 if(id == 0) 13 { 14 printf("a=%d&&g_val=%d\n",a,g_val); 15 } 16 return 0; 17 }
打印结果:
这个打印结果很简单,这里提问,子进程为什么能打印g_val的值呢?
我们都知道全局变量是在main函数栈帧创建之前就创建了的,按道理来说应该是和子进程没有关系才对,那么,子进程能打印这是怎么回事呢?
这里就得出来一个重要结论,父进程的数据,默认能被子进程看到并访问!
我们现在有了两个预备知识:
1 main函数的命令行参数可以使用,命令行参数是我们输入得来的
那么作用是什么?不要忘记我们ls的不同选项了,是不是就可以结合命令行参数,类似于通过下标的方式,来执行不同的功能了?这就是命令行参数要做的事。
2 父进程的数据默认能被子进程看到并访问
第三个知识是,我们平常输入指令的地方,也就是命令行解释器,提问,我们是输入给谁的呢?为什么我们输入了对应的指令,就可以有相应的指令运行呢?
这里,就是命令行解释器,我们平常输入的ls cd等命令,输入了之后,在进程部分我们知道它们实际上也是一个一个的进程,那么它们的父进程是什么?
不管我们写了任何的代码,不管怎么运行,不管我们使用的是任何的指令,不管带了多少个选项,它们本质都是进程,我们打印出来它们的ppid,在ps里面,查对应的ppid,就发现是bash进程。
这个场景是不是很像VS中cin的时候,等待输入?
所以实际上我们的操作都是对bash进程操作的,虽然我们是运行了子进程,但是本质都是给的bash进程。
预备知识3:
3 命令行给的指令等,都是传给bash的
环境变量
3个预备知识已经就位,首先提一个问题,ls运行的时候是进程,我们自己写的代码运行的时候./test也是进程,它们两个都是子进程,凭什么我们自己写的代码就需要./ ,要在当前路径寻找呢?ls等指令就不需要呢?
直接看现象
我们直接引出结论,因为有PATH这个环境变量的存在,我们可以使用echo打印出来看看:
这样是不行的,我们需要$符号:
我们可以看到有多个路径,以冒号为分隔符。
而且可以看到,ls的指令是在bin目录里面,在PATH里面恰好也有,而查找命令就是根据PATH里面有没有对应的路径,如果没有对应的路径,就需要我们自己指定路径了。
这是PATH的用途。
这样更加直观,我们可以指定路径,来执行我们所写的代码。
这是直接看现象部分,Linux中有全局设置,即是告诉编译器,应该去哪里寻找命令。
那么我们如果想要和系统一样,直接执行呢?也就是说我们要修改PATH,或者说将可执行文件放在PATH里面的目录里面,我们先来试试第二种,将我们对应的路径加进去:
使用该指令,将我们的可执行文件所在的文件路径加进去即可,然后就可以执行运行。
这里有一个需要注意的点,test在Linux中是一个命令,如果我们的可执行文件是test的话,就会导致结果容易混淆,这里推荐将可执行文件换个名字:
此时就好了。
这是第二种,我们来试试第一种,直接修改PATH:
此时我们直接修改,然后会发现PATH的路径只有我们赋值的,这是一种覆盖:
但是此时就会发现,bin目录的,或者是说原来的路径里面的指令都使用不了,但是我们的main还是可以使用:
好了,是不是感觉麻烦来了?什么指令都用不了了,此时,有同学会注意到为什么echo还可以使用?
这里就先埋个伏笔,我们通过这个现象,引入一个问题,我们打印出来的Hello world是来源于内存还是磁盘呢?
答案不用多说,自然是内存,那么我们使用指令的时候,bash进程肯定是要先找到指令,那么去哪里找呢?结合我们打印Hello world,我们知道是先将字符串加载到虚拟地址,也就是到了内存里面,同理,bash找指令的时候,是不是应该到虚拟内存去找?
当我们登录到Linux系统的时候,许多配置文件是先加载到了bash进程,那么环境变量是否在配置文件里面呢?当然是,所以我们能看到的PATH不过是内存级的变量,如果我们重启一下,那么我们对PATH的修改就没有用了,因为配置文件我们没有修改。
那么对于配置文件和指令,我们可以有一个结论,因为bash要执行指令的时候,需要找到该指令,那么配置文件就一定比指令先加载到bash进程里面。
既然提到了配置文件,我们不妨看几个:
对于vim来说,viminfo就是它的配置文件之一,所以我们yum的时候,是十分方便的,因为它会帮我们下载对应的配置文件,我们就不用单独配置文件了。
更多的环境变量
我们现在了解了一个环境变量,我们来看看更多的环境变量:
因为图片大小原因,这里截图部分,我们使用指令env即可查出来一张表,环境变量表,里面包好了所有的环境变量,我们每个简单介绍一下:
SHELL变量,外壳程序,可以理解为bash是它实例化的一个对象,HISTSIZE历史大小,表示该机器能存储我们指令的最大个数,我们平时使用指令是可以上翻下翻找我们使用过的指令的,那么查找是有个度,就是这个环境变量,是我们存储指令的最大数目,默认是1000.
可以使用指令history来查看我们写过的指令。HISTTIMEFORMAT历史时间格式,PWD代表当前路径,它是可以变化的,因为我们不管在哪个路径下是用pwd都可以打印当前路径,就是因为它是变化的。HOME不用多少,当前用户的家目录。
带有UTF的环境变量是表示当前的编码是什么,比如这台机器的编码就是UTF-8,下面那个让人头皮发麻的是颜色设置。
还有这些,比如PATH,USER等。
以上就是其他的环境变量。
那么我们能不能自己来点环境变量呢?当然是可以的:
难道这么简单?
这也没有找到啊?
为什么又可以打印呢?
哦~原来是因为HELLO是个本地变量,我们只需要知道一下本地变量就可以了,没啥用这东西,那么我们如何让HELLO变成环境变量呢?
export即可,如何取消我们设置的环境变量呢?
unset即可。
尝试理解环境变量
我们首先来看一个函数,我们想要通过代码的形式,来看到环境变量,所以我们使用environ,它是一个二级指针,不出意外,指向的是一个char*的数组,那么我们打印看看:
int main(){ extern char** environ; for(int i = 0;environ[i];i++) { printf("env[%d]:%s\n",i,environ[i]); } return 0;}
欸!这个东西,怎么那么,像我们刚才看到的环境变量?对!它们就是我们刚才看到的环境变量。
那么结合我们的预备知识2,父进程的数据可以被子进程看到,我们知道最开始加载到了bash进程环境变量,可以被子进程打印出来,那么在bash里面是如何对诸多环境变量进行管理的呢?
这里的environ是二级指针的数组,供子进程使用,我们不难猜测,bash进程组织环境变量是靠的一张表,char* env里面存储的就是所有的环境变量,而environ指向的就是env数组,那么就可以通过下标的方式进行打印。
既然数据都共享了,父进程不妨直接给子进程两张表,一张表是命令行参数表,一张表是环境变量表。
此时,我们在命令行解释器输入的-a使用到了命令行参数表,环境变量表我们可以这样:
int main(int argc,char* argv[],char* env[]){ for(int i = 0;env[i];i++) { printf("%s\n",env[i]); } return 0;}
所以引入了main函数的第三个参数,env环境参数表,这里使用env[i]作为判断条件,我们还可以证明一件事,argv[] env[]都是以NULL结尾的。
看一个库里面的函数,叫做getenv,简单的,我们直接实例代码即可:
int main(int argc,char* argv[],char* env[]){ char* str = getenv("PATH"); if(str) { printf("%s\n",str); } return 0;}
简而言之,getenv就是用来查找环境变量的。
所以现在查找环境变量有三种方式,一种是getenv,一种是main函数的参数,一种是environ二级指针。
我们现在进入最后一个话题,为什么我们最开始修改了PATH,但是仍然可以执行echo命令呢?
我们如果export一个环境变量,env里面可以查找到:
但是我们结合父进程的数据能被子进程看到,但是子进程的数据不应该被父进程看到这个点,难道bash执行export不会创建子进程吗?
是的,如果创建了子进程,bash的env表就是不能看到HELLO,所以这种指令是特殊指令,叫做内建命令,是由bash自己来执行的,不是创建了子进程的。
到此,环境变量就结束了!尚未不清楚的点后续更新~
感谢阅读!