1.命令行参数
(1)main函数的参数int argc和char* argv[]是什么?
main函数可以带参数,即int main(int argc, char* argv[]),(int argc, char* argv[])叫做命令行参数列表,int argc叫参数的个数,char* argv[]叫参数的列表。
当我们在Linux中输入./test执行程序时:argc == 1,argv[0] == "./test",当程序加载时我们写的一行字符串都会被根据空格拆分成多个小字符串,作为参数传进argv
下面就是个很好的例子
我特地在打印时使用了选项1、选项2来标识每个被拆分的字符串来作为引导。事实上,指令的本质就是C语言,这其实就是ls -a -l这种指令选项实现的原理。如test -opt1 -opt2 -opt3就会被转为4个字符串,分别对应argv的4个元素argv[0] ~ argv[3]。在main函数内显式调用argv[1]、argv[2]...,通过字符串比较对这些argv[num]字符串进行匹配,进而根据判断语句实现不同功能匹配。
在Linux指令中,命令行参数使得程序能有不同的功能和表现。但在我们自己平时写的代码中,很少用到这种功能,所以一般main函数不写参数。
(2)命令行参数是如何传递的?
ls -a -l...这一行被视为一个字符串,这个字符串首先被用户的父进程Shell(Linux中是-bash,CLI的具体实现程序)拿到,父进程将这个字符串按照空格分解为多个字符串,进而构建出了argc和argv(argv是动态分配空间的,根据拆分字符串数量决定开辟空间,argv末尾有结束标志,如argv[8] == NULL)。也就是说实际上最先拿到argc和argv这两个数据的是父进程-bash。
当父进程创建子进程,也就是调用开始程序的时候,子进程会独立一份代码和数据,保持独立性,其中这部分数据里就有命令行参数列表argc和argv。也就是说bash启动程序形成的argc和argv列表其子进程也能看到。
这里需要注意的是,main并不是程序入口,main函数也是被其它函数调用的,在main函数前还有CRTstartup(),这个函数就会针对父进程传下来的数据调用main函数并决定是否传参数。main函数结束后的返回值也是交给CRTstartup()的。
2.环境变量
(1)main函数的第3个参数char* env[]是什么?
main函数的参数其实还有一个char* env[](环境变量表的指针,实际上是char** env)
这是另外一张表,每个元素都是一个环境变量,env[0]、env[1]......即可全部调用。最后一个元素之后同样有NULL标志。所有环境变量的格式都是key=value,如USER=SGlow,这看上去似乎是在展示某种属性,事实上环境变量是我们很多进程能运行起来的重要基础。很多指令和程序的执行都依赖环境变量里面的信息
使用env指令可以查看当前环境变量
(2)环境变量的全局属性
环境变量具有“全局”属性。具体地说,环境变量表继承自父进程,也可以继承给子进程。当我们用户登录Shell时,-bash进程被创建,此时它就拥有了一份环境变量表。不论这个用户以任何形式创建进程,归根到底这些进程都是bash的子进程,于是所有的进程用的环境变量表都是一样的。
我们可以在命令行使用env来对比test.c打印出来的环境变量表,发现是一样的。
因此我们称其为全局属性。至于环境变量出现的目的以及更多细节,我们后续会提到。
(3)借助PATH属性进一步了解环境变量
我们要思考一个问题:./test可以运行程序,但为什么test就不行呢?我们现在已经知道ls、pwd的本质也是C语言写的可执行程序,那为什么那些指令执行直接输入可执行程序名就可以了呢?
事实上,我们输入的每一条指令都会到/usr/bin等规定目录下去找,如果这些目录找不到就必须使用./test来指定调用程序了。像ls、pwd这些C语言程序就是如此,因为系统能找到这些程序,所以不用./ls或者./pwd
下面是/usr/bin目录下的一些指令对应的可执行程序
我们如果将我们写的可执行程序放到这个目录里,那就自然可以按指令的形式顺利运行了。
注意我们要先使用type (可执行程序名)看看是否和已有命令冲突,如果冲突了就要换名字。
但这不是唯一的方案,系统也不会只到这一个目录下查找。PATH环境变量就指定了系统以何种顺序进行查找
使用echo $PATH就可以查找当前环境变量的PATH。$是一个标志,后面跟的就是要查找的属性名
我们可以使用PATH=$PATH:$PWD来加上我们要执行的程序的目录。PWD也是环境变量的一种,它指向了我们当前的工作目录,指令pwd就是读取该环境变量。
需要注意的是PATH=(路径)会直接将右侧字符串替换回去,所以我们要保证格式规范,还有不要直接PATH=$PWD,这样默认路径就被覆盖了。这会导致一些指令无法运行(注意不是全部)
由于命令行是在-bash这个进程下进行的,所以每当我们cd时,环境变量的PWD就会修改,当有程序运行起来时,就会读取当前的PWD作为自己的cwd进程属性,这就是为什么bash的子进程都知道自己的cwd的原因。如果这些子进程再创建子进程,它们也会继承父进程的环境变量,知道自己的cwd。当然之前也说过,在代码中我们可以使用chdir("(路径)")来显式改变cwd,但我们要知道默认路径的根本是读取bash进程下的PWD。理解程序运行起来时进程属性cwd是如何确定的对我们深入理解环境变量有很大帮助。
(4)环境变量的配置
PATH=$PWD会覆盖默认路径,怎么抢救呢?事实上,我们只需要重新登陆系统即可解决。因为环境变量是内存级别的,我们指令修改PATH是修改的bash进程对应内存空间中的环境变量数据,而磁盘上的数据不会受到影响,所以退出重进就能完全恢复。
登录时,系统首先要创建bash进程来启动CLI,保证命令行的正常执行,所有用户登陆后都要干这件事。创建bash进程分为创建PCB和从磁盘中读取环境变量配置文件信息到内存两步,读取配置文件的本质是为了在内存中配置自己的配置文件,因为不同的用户初始时有不同的环境变量,像PWD、HOME、USER这些都需要按用户设置。
配置完环境变量后bash进程就基本创建完成了,各个环境变量都有值了,像HOME和PWD最开始都是指向自己的家目录(是因为HOME存的是这个值,所以那里才叫家目录)。同时bash也是一个进程,这个进程也有自己的cwd,它会使用类似chdir()的操作设置自己的cwd,这个cwd就是刚刚读取到的配置文件中HOME的路径,即家目录。
我们发现环境变量对于每个用户是独一无二的,是用户登录后需要尽快导入的数据,在bash进程的创建中也有很重要的作用。当我们理解清环境变量的内存级属性,以及它和bash之间的关系之后,我们对环境变量又有了一个新的认识,也对之前的进程的属性的认识更上一层楼。
但还有一些问题,这个配置文件在哪里?我们可以自己改吗?
这个配置文件在我们家目录下,当登录时会读取.bash_profile和.bashrc到内存中以修改形成当前用户的环境变量信息。因为.bash_profile和.bashrc在这个目录下,所以读取文件时的HOME就被默认设置为了该路径,因此配置的环境变量HOME就是该路径,导致家目录是该路径。家目录被设置在该目录的根本就是因为.bash_profile和.bashrc在这个目录下
在.bash_profile里面我们可以看到PATH的默认信息,这是存储在磁盘上的,每次登录都会被读取到内存上。我们可以加上自己的想要查询的路径,保存退出后,用source .bash_profile使配置文件生效,之后登录就能自动加到PATH环境变量
我们重新登录就会发现我们添加的默认信息已经生效了,当环境变量被配置时,我们的自定义路径就会在PATH中,我们之后就无需手动PATH=$PATH:(路径)
(5)一些环境变量的功能
在进程属性中我们能查到是谁启动的进程(uid),从而和文件的拥有组、所属组、other的uid匹配,进而控制权限。但是系统怎么知道谁启动的进程呢?就是USER=(用户名),当登录后导入环境变量,创建bash时系统就知道我们是谁了,不管创建多少进程,由进程数据的继承规则,系统永远知道我们用户的身份。
SHELL告诉我们登录时启动的Shell进程对应程序的路径,因为一个系统中可能有不同的Shell版本;LOGNAME和NAME一般来讲是一致的,是指当前的登录用户;OLDPWD是指上一条cd路径,它显然直接帮助cd - 这个指令实现
(6)环境变量的读取
env是整体查看,获取具体的信息可以使用管道 + grep筛选
getenv((环境变量key)),返回其value
unistd.h中有一个全局的变量char** environ(指向char* env[]数组),extern声明之后就可以使用
3.本地变量
(1)如何创建和使用
我们直接在命令行使用a=5,b=10就是在定义本地变量。我们可以借助while和for循环来进行一些批量化操作
其中说明(( 表达式 ))是算术表达式,其返回值用于条件判断,$(( 表达式 ))返回用于变量赋值。在(( 表达式 ))的表达式不需要写$(变量),能自动识别本地变量,运算结果也能自动修改变量。[[ 表达式 ]]和[ 表达式 ]还支持一些文本的比较,但都要写$。一般来说,我们略微了解即可。这种用法还催生出了Shell脚本.sh用来处理批量化任务(类似于Makefile),.sh本质也是一个文件,./之后也是按照Shell的逻辑一行一行执行。
(2)本地变量和本地变量表
用户登陆,创建bash进程时,除了环境变量表的导入,bash还创建了一个本地变量表,当我们在命令行使用a=10这种操作时a被直接加到本地变量表。
export a可以将本地变量mv到环境变量表中。export操作相当于直接将指针移动到环境变量表,移动到env中后本地变量表就没有这个变量了。
通过export,我们也可以使用export b=100直接添加到环境变量表中。unset (key)取消环境变量
(3)通过本地变量进一步了解环境变量
环境变量是可以被继承下去的,是一个全局有效的配置信息,每个子进程都能得到这个信息;而本地变量不能,它也不能被子进程看到。
在和本地变量的对比中,我们可以进一步看到环境变量的全局属性。系统的配置信息,尤其是指导性配置信息(当前用户是谁USER,工作路径PWD)都是交给环境变量来保管的,而不是本地变量。环境变量的导入是系统配置起效的一种表现,因为所有程序运行起来都是进程,而所有进程都遵循系统的配置信息,这就说明这个配置信息有了用处,否则配置信息就只能存在文件里,它并没有对系统造成实质性的规范和影响。
和本地变量相比,环境变量更倾向于在进程间传递只读数据,虽然我们可以进行内存级修改,但毕竟环境变量是程序启动的根基,所以一般都是以读的形式访问,这和本地变量有很大区别。