一.命令行参数
在讲解命令行参数这个知识点之前我们先思考一个问题:main函数可以有参数吗?可以有几个参数?是哪几个?
这个问题的答案想必大家心中想必都清楚,确实可以有参数,或者说main函数本来就是有参数的,只不过我们在日常使用时忽略了而已,而我们印象中含参数的main函数应该长这样:


想必上图的这个两个参数就是大家心目中main函数的参数,那么大家想过这两个参数到底是干什么的吗?
可能有人只是知道有这两个参数,但是并不知道它们的作用,毕竟我们在日常使用main函数时也没写过这两个参数,那么在这里我们就要好好说道说道这两个参数。
第二个参数char* argv[] ,这个参数我们看到其类型就知道这是个指针数组,而int argc表示的就是这个数组中数据的个数,那么这个数组中的是什么内容呢?
毫无疑问char*已经说明了问题的答案,这个数组中的指针指向的是一个一个的字符串,那么问题又来了:这些字符串是什么内容呢?我们下面来看看:


通过输出的结果我们发现,这个数组中存储的居然是我们输入的命令,并且我们再看:

我们在要执行的指令后面加上一些选项,数组中的内容同样也发生了改变,并且我们仔细观察的话就能发现,数组中的内容是有规律的:每个位置的数值都是上面命令行中以空格隔开的单独的一个字符串。
那么这时候就有一个问题了:按上面所说,我们在命令行中输入的命令其实都是字符串?
在这里确实可以这么认为,毕竟我们已经通过上面的例子证明过了,那么既然是这样,有人就要问了:**为什么要这么做呢?**我们来看一个例子:


看到上面这个例子,想必都很疑惑我写这是干什么?这不就是把argv数组中的内容按条件判断然后输出出来吗?我们接着往下看:


现在我们对比上面这两种命令,是不是感觉很相似?
相似就对了,我们只是在一些命令后面加上一些选项,执行后输出的内容并不一样,那么你认为凭什么你输入的只是一个字符串,却能输出你想要的内容?
答案就在上面,像上面的./mycmd,which等命令后面跟的一些选项,它们本质是字符串,可以以一定的方式传递给which等内部的" main "函数,在which内部实现的时候,就可以根据不同的选项进行判定,而判定的大概逻辑就和上面差不多,从而实现类似的功能,输出你想要的内容。
而这就是上面为什么要这么做的原因,也就是命令行参数的意义所在,就是为了传给内部的main函数,并根据argv数组中的内容进行判定,从而输出你所期望的内容。
那么这个时候就又有问题了:谁将这些参数传给的main函数?或者说这些参数是哪儿来的?
要解决这个问题,我们要先思考另一个问题:命令行中输入的字符串,谁是最先接收的?或者说谁是最先知道的?
这个问题第一次听的话有些人一时半会儿还真不知道答案是什么,下面我来说:
答案是bash进程,命令行解释器,为什么?
原因就是我们在进程(一)中讲的在命令行中启动的进程的父进程都是bash进程,而子进程的创建是在fork函数核心代码执行后才有的,而传入参数的过程是在fork函数执行之前的,那么就必然是bash进程接收的,大家想一想是不是这个理。
而为什么子进程也有这些参数呢?
这里我们在进程(一)中也讲到了,fork函数之后,虽然分为父子进程,但是我也说了父子进程代码和数据是共享的,有了这层关系在,子进程有这些参数也就不奇怪了。
那么就只剩最后最后一个问题了:bash进程是怎么传给子进程这些命令行参数的?或者说子进程为什么有这些参数的问题解决了,那具体是如何做到的呢?
这里涉及到后面章节要讲的东西,没有办法细讲,我们此时只需要知道bash进程会将这些命令行参数封装成了一个命令行参数表,并将这个命令行参数表传给子进程,子进程进而从这个表中获取所需要的命令行参数,更深层次的细节后面会讲。
二.环境变量
提到环境变量,相信大家都不陌生,如果是计算机专业的同学,我们在安装相关软件时,都要配置相关的环境变量,大概就是这个页面:

我们在自己的电脑上配置环境变量应该都是在这里配置的,配置的过程的甜只有自己心里知道。
同样我们在配置环境变量时,应该都会有这样的问题:为什么要配置环境变量?
而下面我就要围绕这个问题来为大家介绍环境变量的相关知识:
2.1环境变量的定义
环境变量(environment variables)⼀般是指在操作系统中⽤来指定操作系统运⾏环境的⼀些参数 如:我们在编写C/C++代码的时候,在链接的时候,从来不知道我们的所链接的动态静态库在哪
⾥,但是照样可以链接成功,⽣成可执⾏程序,原因就是有相关环境变量帮助编译器进⾏查找。
环境变量通常具有某些特殊⽤途,还有在系统当中通常具有全局特性。
一句话该概括环境变量的定义:是系统级别的一些全局变量,具备不同的用途。
2.2简单的认识一些环境变量
上面虽然说了环境变量的定义,但还是太笼统了,下面我拿其中一个环境变量举例,来帮助大家更好的去理解环境变量的定义。
环境变量: PATH ,这个环境变量想必大家并不陌生,我们在下载软件配置环境变量总是绕不开它,那么它有什么作用呢?我们来看看:
我们又有没想过这样一个问题:为什么我们自己写的程序在执行时前面要加上路径,而执行ls等的指令时却不需要加上路径?我们自己写的程序如果前面不加上路径,为什么会显示没有找到这个命令?
这个问题的答案就与PATH环境变量有关,我们接着往下看:
我们通过echo指令来查看PATH环境变量中的内容,并且查看了ls指令文件所在的路径,发现PATH中包含了ls指令文件所在的路径,那么这是巧合吗?
当然不是,ls指令文件所在的路径在PATH中,而ls指令在任何路径下都能使用,答案已经呼之欲出了,那就是: 操作系统,查找可执行命令,是在环境变量PATH中查找的!!!
而为什么上面我们自己写的可执行命令要加上路径,就是因为它的路径不在PATH环境变量中,操作系统找不到它啊,找不到那当然就会报出找不到该命令的提示。说了这么多,实践出真知,下面我们来证明一下:
我们这里通过export指令将mycmd所在的路径添加到了PATH中,此时我们换到其他路径中发现此时我们再次执行mycmd时不需要加上路径就能执行。
通过上面的例子就证明了我们自己写的程序和ls等指令在执行时产生区别的原因,操作系统在PATH中找到了你要执行的指令的路径,自然在哪里都能执行,并且不需要加上路径。
那还有哪些环境变量呢?
我们通过env指令就可以获取到当前系统中所有的环境变量,我们来列举几个大家熟悉的:
1.PWD
看到这个相信大家都很熟悉,就是我们常用的查看当前路径的指令:pwd,有了上面内容的铺垫相信大家就知道为什么pwd指令能够获取我们当前所在的路径。
这个功能正是通过获取PWD这个环境变量的内容从而实现的。
2.HOME
这个就是我们的家目录,我们通过cd ~指令就能回到当前用户的家目录,而这个指令的实现同样是通过获取HOME这个环境变量的内容来实现的。
3.USER
我们在执行whomi指令时,就会返回当前用户的名称,这个指令的实现也是通过获取USER这个环境变量的内容来实现的。
里面的环境变量还有很多,像LS_COLORS,我们再执行ls指令时,会看到展示出的文件有各种颜色,这个功能正是通过获取这个环境变量来实现的。并且我们看到这个环境变量后面跟了一长串的内容,这一长串内容就是定义在LS_COLORS 时,不同文件类型、扩展名、权限位在终端中显示的颜色方案,通常这个变量由dircolors命令从~/.dircolors或系统默认配置生成。
更多的环境变量就不一一介绍了,感兴趣的可以去网上查一查其他的环境变量适用于那些地方。
2.3如何获取到环境变量?
上面我们讲了很多指令功能的实现都是通过获取环境变量来实现的,那么通过什么方式来获取呢?下面我会介绍三种不同获取环境变量的方式。
第一种:
我们上面只是介绍了main函数的前面两种参数,其实还有第三个参数char* env[],这个和char* argv[]都为指针数组,都是指向了一个一个字符串,而这些字符串就是环境变量的K=V形式的字符串,我们用图来看一下:
里面的SHELL=/bin/bash就是K=V形式,数组中保存的都是这种形式的字符串,而char* env[]数组中的内容是从环境变量表中获取的,就和char* argv[]数组是从命令行参数表中获取内容是一样的。
第二种:
第二种方式呢就是通过函数来获取,getenv这个函数就是做这种工作的,不过每次只能获取一个环境变量,参数就是这个环境变量的名字,我们通过例子来看一看:
这里我们输入PWDz,就能获取到相应环境变量的内容,那要是我随意输入一个内容呢?这个函数的返回值就说明了:
这句话的意思就是如果有这个环境变量就正常返回,如果找不到匹配的环境变量就返回NULL,我们来试一试:
这里可以看到如果找不到匹配的环境变量确实返回的就是null。
第三种:
这第三种方式就是名为environ的变量,我们可以看到这个变量是一个二级指针,那么它指向的是什么呢?
这个变量保存的就是环境变量表的地址,因为环境变量表的类型是char*,那么要保存其地址就得是二级指针,那它该怎么用呢?我们下面来看一看:
我们首先要声明<unisted.h>这个头文件,接着在main函数中要声明这个变量,最后使用的是方法就和上面第一种方式中的char* env[]数组是一样的,毕竟environ就指向指向环境变量表嘛。
上面获取环境变量的方式我们介绍了三种,那么此时就有一个问题:我的进程的环境变量是从哪儿来的?
这里的问题注意要与上面问题作区分,上面的问题是我们获取到了环境变量,并使用了这些环境变量的方式,而这里的问题是当前进程获得的环境变量是哪儿来的?
那么这时候有人就要说了:那不就是从char* env[]中获取的吗?
那么char* env[]是从哪儿获取的这些环境变量呢?
这个问题的答案就和上面我们讲子进程的命令行参数是从哪儿来的是一样的,答案很明显了: bash,和命令行参数一样,环境变量是bash先获取到的,之后再由bash将其封装成环境变量表,进而由子进程的char* env[]数组通过环境变量表来获取内容。
那这个时候又有人问了:bash又是从哪儿获得的环境变量呢?
这个问题的性质和上面命令行参数的不同,bash获取命令行参数是通过我们在命令行输入的内容,而环境变量可不是我们在命令行中输入的内容,那bash是怎么获取的呢?
答案是从 系统的配置文件 中来,是哪些配置文件呢?我们下面来看一看:
是从这个名为bashrc的配置文件中获取到的,不同的系统这里显示的文件可能不同,Centos下不只有这个文件,还有一个叫做bash_profile的配置文件,而Ubuntu下并没有这个文件,但是bashrc这个文件是都有的。
这就是Ubuntu下bashrc文件中的一部分内容,而Centos下该文件的内容也和Ubuntu不尽相同,Centos下该文件的内容没有这么多,具体的内容感兴趣的去查一查。
整个过程就是当我们启动时,系统就会根据当前用户的信息对环境变量进行初始化,接着由bash通过这些配置文件来获取这些环境变量。
2.4为什么要有环境变量?
我们在上面的环境变量定义中就已经揭晓了答案,为了用啊,我们在编程时,有各种各样的需求,正因为有不同的需求,也就有了不同的环境变量,进而这些环境变量会有不同的用途。
上面我们说bash的环境变量是从配置文件中获取的,这句话并不严谨,其实有的环境变量是启动后,动态获取或创建的,下面我们来看一个例子:
我们现在知道了bash的pwd是根据环境变量中的PWD来实现的,那环境变量PWD是从哪儿获得的当前路径呢?
这个PWD就是一个例外,它并不是bash从系统配置文件中获取的,而是动态获取的,那是怎么个动态获取法呢?下面我们来看一看:
这里就要用到getcwd这个函数,这个函数的作用就是获取到当前的工作路径,这个函数的参数是一个char类型的数组和数组的大小。
而启动之后,系统就会调用这个函数来获取工作路径,而就在这个函数执行后就会更新环境变量PWD,而函数名中的cwd我们也不陌生,它是进程内部的一个属性,记录的就是进程当前的工作路径。
而通过这个函数我们就可以实现和pwd一样的功能:
这就是环境变量的意义啊,我们通过使用不同的环境变量就能实现不同的功能。
2.6环境变量的特点总结
在环境变量的定义中我们就说了它们具有全局属性,那具体是什么意思呢?
bash进程从系统的配置文件中去获取环境变量,二我们自己创建的进程能不能使用这些环境变量呢?
答案是可以的,因为我们所创建的进程是bash的子进程,那我们自己创建的进程会不会有子进程,孙子进程等等呢?
当然也会有,那自然也就能使用环境变量了,这一系列的进程我们用图来演示:
这样一层一层下去,不就体现了全局属性了吗?
最后一个问题:那我们自己创建的变量能否成为环境变量呢?
答案是可以的,我们下面举个例子:
我们直接在命令行中定义的话是不会成为环境变量的,而是要通过export这个指令,在前面加上这个指令后,我们自己定义的变量就变为了环境变量。
那上面没有用export修饰的变量是什么呢?
这类变量叫做本地变量,这类变量只存在于bash进程中,并不会被其子进程所继承,下面我们来看一个有意思的现象:
看到上面的例子大家可能很疑惑这是什么意思?我来点名问题所在。
echo是指令对吧,那它要执行的话是不是就要创建进程,也就是bash的子进程对吧,而我们定义的myenv并不是环境变量,是本地变量,并不会被子进程所继承,那echo是怎么打印出myenv的内容的?
这个问题的答案就要涉及到另外一个知识: 内建命令,什么意思呢?
就是当识别到echo时,就会将其自动识别为bash进程内的一个函数,也就是说看似是创建了一个子进程来执行这个命令,其实本质还是bash调用自己内部的函数,自己来执行的。
在Linux中大部分命令是可执行程序,需要通过创建子进程来执行,但是有一部分命令,执行的时候,没有风险,就如上面的echo命令,就只是把内容打印出来而已,没什么风险,这类命令就需要bash自己执行,我们就把这类命令叫做内建命令!!!
虽然我们通过which命令可以查到是有echo这个可执行程序的,但是我们在使用echo命令时还是会将其自动识别为bash进程中的一个函数,并不会创建一个子进程来执行这个命令,也就是我有,但我不用。
这个内建命令在这里只是简单介绍了一下,更深层次的细节在后面的章节中会细讲,这里我们了解一下即可。
以上就是命令行参数及环境变量的全部内容。