【Linux环境变量与命令行参数】常见环境变量 | 环境变量的全局属性 | 命令行参数
前言
本文中主要介绍PATH、HOME、SHELL、HISTSIZE这4个环境变量,其中详细介绍PATH。并理解环境变量的全局属性--环境变量可以被子进程继承,这里要注意和C++中的继承进行区分。其次,介绍命令行参数--mian函数的参数。
1.环境变量的基本概念
在Linux及Windows系统中,存在一种特殊的系统级变量,我们称之为环境变量。
环境变量:一般是指在操作系统中用来指定操作系统运行环境的一些参数。环境变量一般是在操作系统启动之后生成的,这些变量的数据来源是通过某些配置文件来的。环境变量是操作系统为了满足不同的应用场景而预先在系统内设置的一大批的全局变量,这些变量在整个的系统当中,从bash往后一直都会被其他进程访问到。这些环境变量有自己不同的用途。
如:我们在编写C/C++代码时,在链接的时候,从来不知道我们所链接的动态静态库在哪里,但是照样可以链接成功,生成可执行程序,原因就是有相关的环境变量帮助编译器一起进行查找。环境变量通常具有某些特殊的用途,在系统中通常具有全局属性。
如:操作系统在启动时,已经默认从配置文件中读取相关信息,并导入到内存中,构建内存级变量,这种变量就叫做环境变量。操作系统在启动命令行解释器shell时,将PATH导入到shell的上下文中,当我们执行对应的指令时,操作系统就按照PATH指定的路径去搜索对应的可执行程序。
PATH只是一种查找指令的场景,此外,Linux操作系统怎么知道当前的登录用户是谁?Linux操作系统怎么知道当前的主机名是谁?Linux操作系统怎么知道当前使用的shell对应的种类是谁?Linux操作系统怎么知道我们所有的编码方式、配色方案、记录历史命令的条数?Linux操作系统怎么知道shell在运行期间各种动态库、头文件对应的搜索路径。上面所有的问题对应的是不同的领域,有的是帮助我们查找指令、有的是帮我们确认登录用户是谁、有的是确认主机名、有的是确认shell对应的种类的。所以不同种类的场景,就要求操作系统在启动不同的bash做命令行解释时,它就必须得预先设置好一批我们未来可能要用到的变量,这批变量就叫做环境变量。
2. 查看环境变量的方法及相关命令
echo $NAME:NAME是环境/本地变量的名称,查看NAME变量的内容。
env:查看系统中大部分的环境变量。
set:查看本地定义的变量和环境变量。
export:设置新的环境变量。
unset:清除设置的环境变量。
3.自己写的指令与shell指令的区别
由下图可知,我们自己写的代码经编译生成的可执行程序mytest是一个可执行程序,而/usr/bin/目录下的ls、touch、pwd指令也是可执行程序。
我们自己写的可执行程序与系统中的可执行程序,都是可执行程序。那么为什么运行自己写的程序mytest必须得带 ./ 路径呢?而系统中的指令ls等不带路径直接可以运行呢?是因为在运行程序时,需要说明该可执行程序所处的路径,执行一个命令的前提是先找到它。如果我们愿意,也可以使用绝对路径来说明。./mytest ---> ./ 指的是当前路径,即告诉我们相对路径,找到程序mytest。但是执行ls时,就不要带路径,就能直接运行呢?不需要找ls指令的路径吗?答案是需要找,系统默认帮我们找到该路径。
./的意思是当前路径,所以我们就能理解:ls是系统级命令,路径不在当前路径,所以./ls就会报出-bash: ./ls: No such file or directory的提示。mytest是自己写的命令,且在当前路径,所以./mytest可以执行;这里不添加路径系统是怎么找到ls命令的?mytest命令为什么不添加./路径就找不到?此外,执行mytest命令,报出的信息是-bash: mytest: command not found,这里意味着系统曾经找过该命令,但是没有找到,那么系统是在哪里寻找该命令呢?
系统在查找命令时,默认是在环境变量---PATH中查找,而PATH在这里的功能是辅助系统进行指令查找,这是PATH存在的价值。这就解释了为什么ls在PATH中能够找到、mytest在PATH中找不到、./mytest在当前路径中能够找到、./ls在当前路径找不到的原因。也就是说PATH中保存的就是当前指令的搜索路径,我们再来了解一下PATH的路径。
PATH:指定命令的搜索路径。
查看环境变量PATH的方法:echo $PATH。
其中冒号是每个路径之间的间隔符,每个路径都是绝对路径。也就是说系统在执行ls、mytest等指令时,默认会一个路径一个路径的查找,如果找到了该指令就执行,如果没有找到,那么就会-bash: xxx: command not found。我们在执行命令时,系统会自动在这些路径中从前往后依次寻找,找到了就自动执行,而不需要认为说明其所在的路径。即系统执行命令时,操作系统通过环境变量PATH,去搜索对应的可执行程序路径。
使用which指令可以查看部分系统默认指令所在的路径,可以看到ls与pwd的路径刚好包括在PATH之中。
那么我们如何将自己编写的可执行程序在运行时也不需要加上其路径呢?有两种方法。
1、将自己编写的mytest可执行程序拷贝至PATH中的任何一个路径下就可以实现。
当把上一步中拷贝到/usr/bin/目录下的mytest文件删除后,再直接运行mytest。可以发现报的错误与没有将mytest拷贝到/usr/bin/目录下直接运行时,报的错误不一样。如下图:
原因是linux系统把前面拷贝的路径缓存了,所以系统还会去/usr/bin/路径下查找。
2、把mytest当前所处的路径添加到PATH下
export PATH=$PATH:/home/userlq/dir1/,其中新的路径=原来的路径:要添加的路径。此时,就可以直接执行mytest命令了。注意:如果没有$PATH,那么老的路径会被全部替换成要添加的路径。可以看到可执行程序的路径已经追加到PATH环境变量中了,此时,就可以直接执行自己编写的可执行程序了。在其他路径下也可以执行该可执行程序,比如在家目录下执行,系统会帮我们找到路径。
如果PATH=/,就意味着会把PATH给清空了,那么原本系统级的命令ls等就使用不了了,pwd指令能使用的原因是因为pwd不在PATH下。
PATH既然是变量,那么意味着它是可以被赋值的,PATH环境变量最开始的一串默认路径是Linux系统给当前用户在配置文件中加载来的,我们重新登陆该用户时即可恢复默认的路径。
注意:由以上内容可知,普通用户添加的环境变量只有本次登陆有效,下次再登陆时,环境变量列表会自动恢复初始状态,所以大家也不用担心在设置环境变量时出现错误。普通用户修改环境变量列表没什么大问题,但是root用户修改时就需要谨慎了,避免造成严重的后果。同时,不建议随意修改Linux系统中的任何一个配置文件。如果没有修改配置文件,还可以重新登陆恢复原有的默认状态。但是如果修改配置文件,可能会有很多未知的错误出现。
自己写的程序为什么无法被直接执行呢?是因为自己写的程序所在的路径并没有在系统的默认搜索路径下,默认搜素路径就是PATH。为什么系统中的指令能够被找到呢?就是因为环境变量PATH中默认就有系统对应的路径搜索。
通过以上介绍的内容可知,把要添加的路径配置到PATH下时,在下一次登陆时就自动恢复初始状态了,那么如何让它永久存在呢?这里有两种方法:
1、修改相关配置文件,该方法对于小白来说及其不推荐;
2、把程序拷贝到PATH下的任何一个路径下(前文已有演示)。
3.1 PATH环境变量保存在.bash_profile文件中
在登陆linux系统时,系统会默认让你当前的shell进程把对应的.bash_profile文件中的内容执行一次,也就是把环境变量导入到当前的shell中。在操作系统启动时,环境变量的配置就是通过.bash_profile文件加载到bash中,所以一旦操作系统启动成功,在内存中会维护一个变量$PATH,这个变量是内存级的,将这个变量覆盖后没关系,下次重新登陆后bash会重新读取对应的配置文件,将环境变量导入进来。这种环境变量是操作系统提供的、具有全局属性的、往往具备特殊功能的变量,称为环境变量。
上图中的.bash_profile文件中定义了环境变量PATH,并export PATH,即导入环境变量。
我们不需要知道环境变量是怎么来的,因为Linux系统的配置文件中就有相应的环境变量设置。当我们登陆Linux系统时,环境变量就会加载进来。环境变量除了上文中介绍的PATH、USER外,还有HOME(当前用户对应的工作目录)、HOSTNAME(主机名)、LOGNAME(当前用户名)、HISTSIZE(linux一共会记录3000条指令,也可能是1000条)。
echo $HOME查看环境变量HOME里面的内容是当前用户对应的工作目录。执行cd ~指令时,进入的其实是$HOME对应的路径。
echo $HISTSIZE查看系统中能够保存指令的最大数目,即环境变量HISTSIZE的值为3000。
history指令会统计历史上使用的指令,由上图可知总共使用了条指令。执行history | wc -l指令得到的结果是系统中历史使用指令的数目。
env查看系统中所有的环境变量。
由上图可知,USER变量中记录了用户是userlq。接着将用户从userlq切换成超级用户root,此时的用户是root,而且环境变量USER中记录的用户是root。sudo的本质就是将USER环境变量由普通用户改为root。(注:su -让root用户重新登录,就会将root的上下文变量重新加载进来。su 其实就是将身份切换一下,对应的路径没有发生变化。)
pwd指令会读取环境变量PWD中的内容,并显示出来。
4.环境变量
4.1 通过getenv()函数获取环境变量
我们可以使用USER环境变量获取当前用户名,也可以使用getenv函数获取当前用户名,如下代码所示。
切换到root用户,默认会自动进入到root的家目录下;并重新进入到userlq用户下的文件mycmd.c的路径下,执行./mycmd指令。此时,用户依然是root。
USER环境变量的最大意义是可以表示当前使用的Linux用户。
在userlq用户下,指令mycmd的执行结果是:权限不足!即系统判断出当前用户userlq,不是root。下面切换用户到root,并进入到mycmd可执行程序的路径下,mycmd程序正常执行。可执行程序mycmd怎么知道我是否有该文件的可执行权限呢?是因为在执行该文件时,该文件是有对应的拥有者和所属组的,当程序在运行时,会先判断该用户对该文件是否有执行权限。
我目前在userlq用户下,现在我想进入到lighthouse用户下的目录,但是系统拒绝了我的请求。为什么呢?因为系统会根据环境变量USER来做检测的,userlq用户既不是目录/home/lighthouse/的拥有者,也不是该目录的所属组,则是该目录的other,而other什么权限都没有,所以就拒绝了该请求。系统级指令都会做身份和权限认证,身份认证有一个重要的换节就是通过USER环境变量来认证的。环境变量会在不同的应用场景中被使用的。
4.2 定义本地变量
Linux的命令行就是bash,bash是可以定义变量的。如下图所示:
在命令行中写下指令myval=100后,shell读取到指令,就会在内存中申请一块空间,并把该变量以字符串"myval=100"的形式存放进去,最后在shell内部另外生成一个指针指向该字符串。
myval是在命令行中定义的,只是前面没有加上export,所以myval虽然存在,但是并没有被导入表状结构中,这种变量被称为本地变量。如下图所示,使用env函数获取不到myval变量。
本地变量:只会在当前进程(bash)内有效。
而且,本地变量针对当前用户的当前进程生效,是一种临时变量,退出本次登录后就失效了。再去echo $myval发现myval已经失效了。
因为myval不是环境变量,所以使用getenv函数也获取不到该变量:
问题:本地变量能被子进程继承吗?用env查看,发现shell的上下文中是没有的。
结论:本地变量只在shell内部有效,不被子进程所继承,只能bash自己用。
4.3 export指令
export可以对环境变量做定义,并且如果已经存在一个本地变量,可以使用export将该本地变量导成环境变量。
我们在通过指令./mycmd执行mycmd程序时,bash就是一个系统进程,mycmd也会变成一个进程(通过fork函数实现的),mycmd是bash的子进程。环境变量具有全局属性,会被子进程继承下去。那么环境变量为什么要被子进程继承下去?是为了不同的应用场景。比如:让bash帮我们寻找指令的路径、进行身份认证。
4.4 env---显示所有环境变量
使用env能够查到变量myval,而且使用getenv函数也能获取到该变量。
当在命令行中运行./mycmd时,bash就是一个系统进程,mycmd也会变成一个进程。此时,mycmd是bash的一个子进程。该过程是通过fork函数实现的。环境变量具有全局属性,根本原因是环境变量会被子进程继承下去。环境变量为什么要被子进程继承下去?是为了不同的应用场景。比如:让bash帮我们寻找指令路径,以及进行身份认证等。
4.5 set指令、unset指令
如果既想显示本地定义的shell变量和环境变量,使用set指令。set---显示本地定义的shell变量和环境变量。取消一个变量使用unset指令。
定义环境变量时,加不加双引号都可以,因为环境变量都是字符串,建议定义环境变量时加上双引号,因为环境变量可能会带空格。环境变量的名称建议使用大写字母。
4.6 环境变量能够被子进程继承
根据前文的内容可知,我们在执行自己写的命令时,需要带./,执行系统命令,如:ls,不需要带./。找一个文件时,必须使用相对路径和绝对路径。那么下图中的mycmd以及mycmd.c命令是怎么寻找的呢?系统怎么知道mycmd、mycmd.c文件的位置呢?ls是怎么知道mycmd、mycmd.c文件的路径的呢?按道理来说,命令应该写成ls ./mycmd、ls ./mycmd.c,在当前路径下寻找mycmd、mycmd.c文件。
这是因为有个环境变量PWD,里面保存了当前路径。如下图可以查看到环境变量PWD中保存了当前路径,当回到上一级目录时,再查看环境变量PWD,我们发现PWD中路径自动往上退出一步。环境变量是在bash内维护的,即bash内维护了当前用户所处的路径,当路径发生变化时,shell会自动帮你调整环境变量的值。
而运行ls指令时,其实是在创建子进程,而环境变量中保存的信息可以被子进程继承下去,则PWD中保存的路径信息就被ls继承了。此时,ls就知道自己当前在哪个路径下,所以显示文件时不需要带路径。
ls指令是用来显示当前路径下的所有文件,那么ls指令为什么能够显示当前路径下的文件,ls指令怎么知道它在哪个路径下?ls在dir2目录下显示的是dir2目录中的文件;回到家目录,执行ls命令显示的是家目录中的文件;进入到根目录,执行ls命令显示的是根目录中的文件。为什么进入到不同的目录中,执行ls命令能够显示当前目录中的文件?因为在进行路径切换时,当前路径发生变化了,环境变量被所有的子进程所继承,ls指令运行起来也是一个子进程,所以就得到了bash对应的环境变量,所以ls指令只需要找到该路径并将该路径中的文件显示出来就行。
实现PWD指令:
环境变量可以让特定的用户去使用,环境变量的全局属性,环境变量可以被所有的子进程继承。shell命令行除了全局的环境变量之外,还有本地变量,本地变量只在bash内部有效,不能被子进程继承。
4.7 HOME,指定用户的主工作目录(即用户登陆到Linux系统中时,默认的目录)
为什么登陆不同的用户userlq和root时,所对应的家目录不一样?
根本原因是因为Linux系统中还有一个环境变量---HOME,它默认表明当前用户登陆时所处的默认路径,当然它在系统中也有配置。从上图中可以看到userlq用户中HOME保存的是/home/userlq;root用户中HOME保存的是/root。
4.8 SHELL,当前Shell,它的值通常是/bin/bash
命令行解释器有bash、sh等,可以看到SHELL中保存的是当前命令行结束器的种类---bash。
4.9 HISTSIZE
history记录了当前用户历史上所使用的部分命令。
history | wc -l 统计了历史命令有201条,加上自己的一条。
echo $HISTSIZE 可以看到里面保存着3000,按键盘的上和下按键能翻到历史命令,前提条件是系统帮你保存了。如果保存太多的历史命令,对于系统来说也是负担,所以默认最多只能保存3000条。
5.环境变量是如何被子进程继承的?
5.1 main函数的参数
int argc 传入程序中的元素的数量,./程序名 也算是一个元素。
char* argv[] 传入程序中的元素表,由bash制作,传给main函数。
char* envp[] 环境变量表,所谓全局性就是指main函数可以通过此函数获取到环境变量表的信息。其中,char* envp[]是一个指针数组,该数组里面的指针都分别指向不同的字符串,并且最后一个指向有效字符串的指针的下一个指针一定指向"NUL"。
main函数有没有参数,可不可以带参数?如果可以带参,最多可以带几个参数?
在C/C++中写了很多main函数都是没有带参的,实际上main函数是可以带参的,可以带3个参数,下面的代码只传了2个参数。其中int argc是命令行参数的个数,char *argv[]是指针数组,数组中有几个有效元素是由argc确定,所以main函数的前两个参数用来记录的是我们在命令行中上传的参数,我们称这两个参数为命令行参数。命令行参数本质上是把程序名和选项依次传递给argv数组,argv数组的大小以及argc的大小与函数名和选项个数之和一致,一共有多少个函数名和选项,那么就需要定义多大的argv数组,argc传递的数字也是该数量。我们输入的命令行操作./mycmd -a -b -c 整体是一个大字符串,当我们实际在做命令行解析时,需要把大字符串以空格为单位拆成一个一个的字符串。然后,0下标指向./mycmd、1下标指向a、依次类推,最后NULL作为argv数组的结束。这个工作是由shell和系统来做的。
命令行参数数组的元素个数是动态变化的,有几个参数就有相对应的长度大小:
5.1.1 为什么要存在命令行参数?
根据以上代码,我们就可以通过不同的命令行参数,使用程序mycmd内部不同的功能,这就是命令行参数的最大的意义。
根据上面的内容,已知命令行参数的作用在于,同一个程序可以用给它带入不同参数的方式来让它呈现出不同的表现形式或完成不同的功能。例如:ls命令可以根据不同的选项完成不同的功能,这就是命令行参数的最大价值。通过命令行参数,我们可以在同一个程序中设计出不同的业务功能。我们在大部分C/C++代码中不用命令行参数的原因是main函数只有一种功能。
5.1.2 argc可以省略吗?
在命令行中传递的各种各样的数据最终都会传递给main函数,由main函数依次保存在argv中,由argc表明个数。
数组结尾是NULL,那么可以不使用argc吗?不可以,原因有两个:
1、数组传参,一般建议将数组的元素个数带上;
2、用户填参数到命令行,如果想限定用户输入命令行参数的个数,就要用到argc,比如:我们要实现一个简单的加减法计算器 ./cal -add 3 5、 ./cal -sub 3 5,那么argc的大小为4。
命令行参数的意义在于,指令有很多选项,用来完成同一个命令的不同子功能。选项底层使用的就是命令行参数。假如函数没有参数,那么可以使用可变参数列表去获取。
5.1.3 参数envp--通过该参数获取环境变量
char* env[]是main函数的第3个参数,它和第2个参数的类型一样,都是指针数组。指针数组中的指针依次指向不同的环境变量,最后以NULL结尾。main函数被调用时,如果main函数带了参数(int main(int argc, char*argv[], char*envp[])),系统会为该进程传两张表。第一张表是命令行参数表,叫做argv;第二章表叫做环境变量表。所以环境变量就会被该进程拿到了,此时该进程就可以使用环境变量了。可以看到这里./test打印出来的环境变量几乎都是和bash的环境变量一样,说明环境变量的继承是通过第三个参数char* envp[]来继承的。
我们自己导入一个环境变量export myval=2222,运行test程序就能看见传入的环境变量。环境变量myval是导给shell的,而运行tset程序时,是需要创建子进程的,并且将shell的环境变量交给子进程的。怎么交给子进程的呢?可以理解为通过命令行参数将环境变量myval交给子进程的。
如果在命令行前面加上了指令export,那么就会把表状结构中的空余指针指向该字符串。
5.2 getenv函数获取环境变量
常用的获取环境变量的方法是通过函数(getenv())获取。
通过getenv("PWD")可以获取PWD环境变量,利用getenv函数可以实现我们自己的pwd指令。如以下代码:
将myproc可执行程序添加到环境变量中去,就可以实现与pwd指令一样的效果,执行时不再需要带上./了,都可以显示当前的路径。
5.3 通过全局变量environ(char**类型)获取环境变量
1、环境变量表
每个进程在启动时,都会收到一张环境变量表,环境变量表是一个字符指针数组,每个指针指向一个以'\0'结尾的环境字符串。环境变量表用于记录与当前进程相关的环境变量信息。
环境变量表采用字符指针数组的形式进行存储,并使用全局变量指针char**environ来记录环境变量表的首地址,因为环境变量表中的内容是char*,所以要使用char**类型的指针指向该表的首地址,使用NULL表示环境表的末尾。
2、 通过全局变量environ(char**类型)获取环境变量
libc中定义的全局变量environ指向环境变量表,envoiron没有包含在任何头文件中,所以在使用时要用extern声明,表明environ在系统中存在,告诉系统environ这个变量我也要使用,系统在编译链接时帮我把该变量链接到系统中。
可以发现这些指针指向的就是一个个一个环境变脸字符串。
这里我们意识到当函数传参数组时,传递的不是数组本身,而是数组首元素地址,所以在main函数的形参列表中写成char* envp[]形式,本质上是一个二级指针,也就是environ,查看一下man手册:
man environ
可以得知这个表状结构的具体示意图,与前文中的环境变量表一样:
推荐使用getenv()函数来获取环境变量,因为该函数是直接根据环境变量的名字拿到环境变量内容。
6.总结
通过上文内容,我们可以知道环境变量的本质就是内存级的一张表,这张表在用户登录时,由系统给该用户自己单独形成。每一个环境变量都有自己的用途,有的是进行路径查找的,有的是进行身份认证的,有的是进行动态库查找的,有的是用来确认当前路径的等等。每一个环境变量都有自己的特定应用场景。
结论:环境变量针对的是特定的人在特定的场合干特定的事。有许多环境变量存储的是用户的个人信息,不同用户的环境变量表各不相同。我们可以利用环境变量做信息验证,根据不同变量(选项)执行不同的操作。
比如:执行ls指令显示的是当前目录下的文件信息,而ls -a则是显示详细信息。原理很简单,调用ls程序时传递了-a这个选项,使得程序一对比,就知道要执行显示详细信息这个操作。
环境变量具有全局属性,程序运行时,环境变量表会传递给程序使用。
其中,环境变量对应的数据,都是从系统的相关配置文件中读取进来的,我们进入自己的家目录,查看该目录下的所有文件:
可以看到两个隐藏的配置文件,分别叫做.bash_profile和.bashrc,打开.bash_profile,观察内容:
可以发现这里就有环境变量PATH,当用户登录时,程序就会自动执行当前用户下的配置文件,加载完成后,环境变量也就被添加进来了。所有用户每次登录,都会重新加载一遍配置文件。除了用户自己的环境变量,还有一些全局的环境变量是在/etc路径下的配置文件.bashrc中,在系统启动时添加的。
当用户登录机器时,操作系统会给用户创建一个shell,用来为该用户提供命令行解释。shell本身是一个进程,会在shell中维护上文中我们所提到过的环境变量的表状结构。
用户在执行命令时,都是命令行解释器shell帮用户执行的,对应到Linux中,是bash在执行。bash除了可以执行命令外,还能以命令行的形式自定义变量。
用户userlq登陆进去系统,直接执行pwd命令,默认所在的家目录在/home/userlq路径,为什么呢?因为在bash加载时,系统默认就会将用户userlq的家目录记录下来。即环境变量HOME=/home/userlq,记录该路径。
同样,root用户登陆,直接执行pwd命令,默认所在的家目录在/root路径。即环境变量HOME=/root,记录该路径。
cd~就进入到当前用户的家目录了,也可以直接执行cd $HOME指令,直接进入到当前用户的家目录。cd ~命令行参数cd做解析发现是~,直接获取HOME环境变量中的内容,然后cd到HOME环境变量中的路径下。这就是环境变量带来的意义。
问题: