命令行参数
不知道你见没见过main函数的参数?如果你见过,那你知道它的这些参数表示什么意思吗?

main函数的第二个参数是一个指针数组,全程是argumentvector,命令行参数列表,第一个参数表示的是argumentcount,命令行参数个数。
我给它编译运行多次看看结果是什么?如下图所示,其实我们输入的命令行指令,OS会认为它是一个完整的以空格分隔的字符串,根据空格,将它拆成一个个的字符串,每个字符串存入argv里,字符串的个数就是argc的值,这个过程是由bash和系统自动完成的。

那为什么会有命令行参数?命令行参数的本质应用是为了实现一个命令,可以根据不同的选项实现不同的子功能,这也是linux中所有命令选项功能的实现方式。说白了就是我现在把你输入的命令以及命令的选项全部都存入argv里了,那通过下标不就可以获取不同命令的选项,不同命令的选项不就是该命令的子功能嘛(linux里的指令本质就是二进制可执行文件,里边肯定是有main函数的呀)。
细节1:命令行参数至少有一个,即argc >= 1,因为你./运行得时候最起码得输入文件名,argv0一定会有元素,指向的就是程序名。
细节2:命令行参数就是一个个的选项(Linux指令的那些选项),以空格分隔的字符串,一个字符,也是字符串。
细节3:命令行参数总共有argc个,argvargc - 1是最后一个,argvargc == NULL(这是一定的)。
补充问题:cat filename如何打印出文件内容的?cat和filename本质都是命令行参数,指令的本质是一个二进制文件,main函数获取了filename这个选项就可以用fopen打开(Linux是用C语言写的),读取到文件的内容后printf打印出来。
环境变量
第一个问题
我们知道Linux系统里的指令本质是一个二进制文件,其存在于/usr/bin/xxx目录下,当我们输入指令的时候,Linux会自动去该路径下找,找得到就执行,找不到就报错。现在我们自己写了一个文件叫mycommand,编译运行后要想执行它就必须要指定路径./mycommand,./就是相对路径,本质就是告诉OS,用户执行的bin就在当前目录下。问题来了,Linux怎么知道就要去/usr/bin/xxx底下找?它会不会去别的路径底下找呢?在Linux系统里会有一个叫环境变量的东西,对于这个问题来说其相关的环境变量叫PATH。
在Linux,会有一种变量,是一个在系统内全局有效的变量,PATH就是一个环境变量,它这个环境变量的作用就是告诉Linux,应该去哪些路径下找用户输入的可执行文件(如果用户没有指明路径的话),PATH就可以获取到PATH里内容(你可以理解为类似于指针解引用一样的效果)。

有了以上的认知,对于我们自己写的可执行程序,如果你执行的时候不想带路径的话就有两种做法了,第一种就是将你这个可执行文件加入到PATH里出现的任意一个路径底下,因为只要你没有指定路径执行可执行文件,OS就会去PATH里出现的每一个路径底下依次去找你这个文件,没有就报错。第二种就是你直接将你当前这个二进制文件的完整路径加入到PATH里边,这个就相当于在配置环境变量。
如果你PATH=/home/xxc/code,这样就相当于是赋值了,就直接将PATH里原来的路径全部覆盖了,这会导致系统里的指令都直接执行不了了。
正确做法是PATH=$PATH:/home/xxc/code,先获取PATH里的内容,加上冒号,在后边添加你想添加的路径,最后再全部赋值给PATH。
常见的环境变量
环境变量就是系统级的变量,变量名和变量内容往往具有全局属性。接下来我们通过env指令来看看几个常见的环境变量。
HOSTNAME=VM-0-12-centos//当前系统的主机名
SHELL=/bin/bash//记录当前系统的bash是什么bash
HISTSIZE=3000//当我们在Linux里输入指令的时候,按上下箭头可以查看历史使用过的指令,但是随着我们输入的指令条数增多,系统记录我们所输入过的指令也是有上限的,这个上限就是HISTSIZE,Linux最多会记录用户最近输入的$HISTSIZE条命令。
OLDPWD=/home/xxc//cd -可以回到上一次所在的路径,那系统是怎么知道上一次所在的路径是什么呢?OLDPWD这个环境变量里存着。所以cd -等价于cd $OLDPWD。
SSH_TTY=/dev/pts/0//当前账号所登录的终端文件,之前博客里提到的Linux下一切皆文件,当前输入命令的终端(XShell打开那个黑色界面,用于输入指令的)也是一个文件,为什么我用echo打印就会打印到我这个终端,因为SSH_TTY这个环境变量里记录了。
USER=xxc//当前哪个用户在登录
HOME=/home/xxc//当前用户的家目录
现实世界里,我们人生身上也会有类似于环境变量的东西。我们的出生年月,籍贯,身份证号......这些都会伴随我们的一生,环境变量具有全局属性是因为这些名字都不会变,要变的就是里边的内容,比如你籍贯改了,你的学历变了......说白了环境变量就是把你可能用到的东西先记录下来,将来要使用的时候直接可以用,就比如你找工作的时候要学历,籍贯,身份证.....。
环境变量和C/C++代码和进程的关系
1.如何用代码获取环境变量?
由上文可知,main函数有2个参数表示命令行参数和其个数,其实main函数还有一个参数叫char* env\[\],它跟argv一样,也是以NULL结尾的,它表示的是环境变量表,其存在的意义就是把环境变量传递给进程。再通俗一点,环境变量是会形成一个表格的,env这个指针数组可以获取到它,然后通过env的下标可以获取到环境变量,每一个环境变量的名字加上其内容会被视为一个完整的字符串存在env数组里。如果不想写main函数的参数,我们也可以通过C/C++内部提供的全局变量environ来获取环境变量,environ就是一个二维指针指向env数组。上边两种方式的本质是一样的,不怎么常用,因为似乎这两种方式只能便利全部的环境变量,要想获取某一个环境变量还得去便利环境变量表。

getenv是我们常用的方式,它的参数表示环境变量名,返回值就是环境变量的值,它的实现逻辑就是根据用户输入的环境变量名去便利环境变量表,然后获取环境变量的值,找不到就返回NULL。

不同的环境变量有不同的应用场景,方便我们编程的时候使用。
2.main函数参数的那两张表是谁传递过来的?
我们知道main函数可以接收两张表,分别为命令行参数列表和环境变量表(全局数组)。这两张表默认是在bash当中的,bash也是一个进程,它在内存里,因此那两张表都在内存里,是两张临时的表,说白了在bash里,它们就是两个全局数组。我的程序、命令都是bash进程的子进程,而子进程可以看到父进程的数据的,父子进程共享代码和数据(其中数据共享的前提是不去修改这个数据)。意思就是我们运行程序的时候,bash会fork创建子进程,通过fork的返回值可以分流父子进程,但子进程可以看到父进程里边的全局变量,所以你即便不给main函数参数,我也能用getenv或者environ去获取环境变量。所以环境变量是父进程给你这个进程的,子进程可以继承父进程的全局信息。
升华:每一个进程都能获取全局变量,因为每一个进程都是直接或者间接是bash的子进程,而子进程可以看到父进程的代码和数据,所以环境变量是可以被每一个进程看到的,它具有全局属性。
那子进程的环境变量直接或者间接是从bash那里来的,那bash进程的环境变量又是从哪里来的呢?答:是从Linux系统的配置文件里来的。我们在启动OS的时候,其实是在启动bash进程,它会去读取系统里的配置文件,并且把配置文件加载到自己进程的上下文里,说白了就是我在bash里先new一个数组,然后fopen打开配置文件,按行读取里边环境变量相关的内容,把读取到的东西按行变成一个字符串存入数组里,这也就解释了为什么上文我们修改了PATH之后再重新登录一下就又恢复原样了,重新登录的本质就是重新把配置文件加载到bash里,这也就表明了我们所谓的修改环境变量是临时修改,我们没有去修改配置文件,所以重新登录重新加载,一切都恢复原样了。
那什么是配置文件呢?bash只有一个,但是每个用户都有家目录,家目录里都会有两个隐藏文件,它俩就是配置文件,.bash_profile里是访问的是.bashrc,而.bashrc里访问的又是/etc目录下的bashrc(我们登录时系统下的配置文件),然后这个文件里又会去访问其他的配置文件,并且什么umask,什么环境变量会在里边提前设置好。(用vim打开这几个文件来看看就知道我在说什么了)。bash启动的时候,就会执行对应用户家目录里的那几个文件。每一个用户登录的时候,系统都会启动一个bash,所以一个用户配置文件的改动不会影响到其他用户,自己家里的事,跟别人无关。

3.环境变量与本地变量
我能不能自己配置环境变量?假设我现在命令行输入TEST=123456,env打开,发现里边并没有TEST,但是我echo TEST是可以的。此时的TEST叫做本地变量,你可以将它理解成就是bash内部new或者malloc的一段空间,变量名叫TEST,内容是123456这个字符串。

如果想将一个已经存在的本地变量变成环境变量,我们用export TEST即可。那这个过程是在干什么呢?说白了就是将本地变量加入到环境变量表里,这是内存级别的,不影响配置文件。如果是加入一个不存在的本地变量到环境变量表,直接比如说export aaa=123即可(注,用export加入的环境变量也是可以被获取的)。unset 环境变量名字,可以取消环境变量,就是把指定环境变量从表里删掉。综上:所有使用指令对环境变量的操作是内存级的,不会影响配置文件,子进程能获取父进程里的环境变量,环境变量具有全局属性的本质就是可以被子进程继承。相对应的,本地变量无法被子进程继承,不具备全局性,只能在bash内部访问。本地变量与环境变量的区别就类似于C++里的局部变量与全局变量。
