每一个不曾起舞的日子,都是对生命的辜负。
环境变量
本节目标
掌握环境变量的相关知识。
1. 环境变量
1.1 环境变量的概念
1. 什么是环境变量?
环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数。
2. 为什么会有环境变量?
在Linux系统中,我们发现我们在执行一些指令时,比如ll等指令,直接就可以输入ll指令获得结果,而对于我们自己编译的程序,比如make之后的文件,就需要
./文件
才能执行。对于执行一个命令来说,我们知道,命令事实上也是一种文件,对于执行这个文件,我们就需要先找到这个文件,在将这个文件执行。而对于ll来说,其对应的文件事实上已经是Linux系统分配到全局的,也就是在默认路径(根目录),因此不需要指定路径搜索这个指令对应的文件,直接ll就代表从根目录查找从而执行。而对于make生成的程序,我们知道这个可执行程序是在当前路径下,并不在根目录中,因此我们也就无法直接输入文件名从而执行,而是需要找到这个文件对应的路径再去执行,因此
./
就代表着当前路径,而
./此文件
就代表找到这个文件的路径并且去执行它!
command not found就意味着并没有从根目录找到这个命令对应的文件。
经过上面的描述,我们知道,如果我们将可执行程序也复制到根目录,那么就也可以直接输入文件名就可以执行该程序了。下面演示一下:
但是这么做是不好的,因为我们在根目录下拷贝就相当于下载了一个文件,这样不经过测试的指令程序事实上会污染系统的指令池的,因此为了改善这种方式,就有了环境变量的方式去处理这种问题!
(在此之前,需要删除我们刚才已经拷贝的文件,指令:
sudo rm /usr/bin/myprocess
删除成功。)
1.2 环境变量PATH
为什么能够在系统中找到对应的指令呢?事实上系统中存在一个环境变量PATH,它能够去检索对应的指令,找到了就会去执行,找不到就会像上面一样提示not found,因此我们如果想用另一种方式去直接执行文件,就可以将这个文件路径添加到环境变量PATH中。
当我们查看PATH变量时,每一个:所间隔的就是一个检索的路径,因此只要把想直接执行的文件的路径也添加到这个里面,就能够检索到从而执行。
当然这里有一个小插曲,如果我们直接通过指令:
export PATH=/home/cfy/sbl/lesson13
,虽然可以添加到PATH中,但这样的话,PATH中的其他路径也就被覆盖了,这就导致一些系统的指令也用不了,当然这也是可以解决的,我们只需要重新登陆这个系统,就可以恢复过来,因为这些环境变量是保存在内存中的,属于内存中的环境变量。
通过指令 :
export PATH=$PATH:/home/cfy/sbl/lesson13
就可以将这个指令追加到PATH环境变量中。
1.3 其他常见环境变量
- HOME : 指定用户的主工作目录(即用户登陆到Linux系统中时,默认的目录)
- HOSTNAME:主机名
- USER:当前用户名
- PWD:当前系统路径
- HISTSIZE:shell 能记忆的最多历史命令的条数
2. 和环境变量相关的命令
1.echo: 显示某个环境变量值
2.export: 设置一个新的环境变量
3.env: 显示所有环境变量
4.unset: 清除环境变量
5.set: 显示本地定义的shell变量和环境变量
对于echo和export我们已经知道其具体是如何操作的了,那么对于后几个,我们先来看看env
2.1 env 命令
env: 显示所有环境变量
1.观察内部:
我们通过env命令,就可以显示这些所有的环境变量,事实上是可以通过代码来模拟的,我们拿其中的USER来演示。
2. getenv函数
即我们可以通过调用getenv来调用这里的变量,那我们先来看一下getenv的使用条件及内部参数。
通过
man getenv
指令:
我们发现返回值是char* ,即满足条件返回相应的值,不满足则返回NULL。
那我们可以利用getenv来编辑代码了:
#include<stdio.h>#include<stdlib.h>#defineUSER"USER"intmain(){char* who =getenv(USER);printf("user: %s\n", who);return0;}
即这样就可以获取到对应的身份。此外,如果我们su -到超级用户(root),我们同样执行这个mycmd,他就会出现user: root的结果。因此,在我们执行一些命令的时候,涉及到权限的问题,其中系统就会通过USER来判断你的身份,从而显示你能否调用一些需要权限的指令。
扩展:
我们发现,bash可以直接给变量赋值,但是env并不能查到,因此这种变量只能称为shell当中的本地变量,即所谓的局部变量。我们同样通过代码验证:
#include<stdio.h>#include<stdlib.h>#defineMY_ENV"myval"#defineUSER"USER"intmain(){char*myenv =getenv(MY_ENV);if(NULL== myenv){printf("%s, not found\n", MY_ENV);return1;}printf("%s=%s\n", MY_ENV, myenv);return0;}
即这样同样说明了在全局中找不到对应的myval。
2.2 export命令
export: 设置一个新的环境变量
在PATH中我们已经知道,export命令可以在PATH环境变量中添加相应的路径,即可以全局查找。但我们看看下面的这种格式的命令:
与之前不同的是,这个指令里面没有=和:等符号格式,但是我们发现,仍然可以通过这种方式使得myval能够查找到。事实上,这并不是将myval添加到了环境变量中,而是因为如下原因:
我们知道,bash是一个系统进程,并且bash进程是所有创建进程的父进程,即我们./mycmd的mycmd在运行时,mycmd也会变成一个进程(fork),而bash就是mycmd的父进程。而环境变量具有全局属性,也就是说环境变量本来就是定义给bash的,但是bash的环境变量会被子进程继承下去(为什么要继承,为了不同的应用场景 ——让bash帮我找指令路径,身份认证),也就是说子进程能够使用bash的环境变量,因此mycmd能够使用bash的环境变量。而myval本地变量能够使用bash的环境变量的前提是只在当前的进程(bash)内有效。
2.3 set命令
set: 显示本地定义的shell变量和环境变量
我们发现,我们所创建的本地变量(局部变量)不能被env找到,但是却可以被set找到,这也就说明了set能够找到shell本地的变量和环境变量,也就是局部变量和全局变量。
而对于set本身,因为里面既有环境变量又有本地变量,因此其内容远多于env,对于类似于续行符
\
这样的符号,其内部也存在。
2.4 unset命令
unset: 清除环境变量
我们在上一部分中所创建的本地变量myval仍然存在,虽然是本地变量,但由于bash的存在也就是父进程的存在,可以将此myval看成是环境变量,因此我们才可以将其显示。此外,如果我们想删除环境变量,就可以通过unset的命令删除。(myval从理解上也可作为环境变量被删除,因为bash存在,myval就有bash所对应的属性)能否被删除,这个和本地或者环境变量无关,一个东西能被添加,就一定要能被删除
即这样就将环境变量删除了。
注:对于export定义的本地变量,实际上是字符串类型,并且可以加双引号,也可以不加双引号,但是对于字符串来说,建议加上双引号,因为字符串可能会有空格这种字符。
3. 环境变量的意义
我们拿ls 指令举例:
我们发现,ls 后的指令为什么不需要
./
,而是直接输入文件名就行了呢?也就是为什么ls就知道这个文件的当前路径在哪里呢?
实际上,这是因为我们有一个环境变量PWD,通过PWD就能够找到对应的路径:
我们发现,当我们
cd ..
到什么路径,PWD就会变成什么路径。因此,ls 就是通过这个环境变量找到了这个文件。
既然环境变量中有PWD,那我们也可以自己实现PWD指令:
#include<stdio.h>#include<stdlib.h>#definePWD"PWD"intmain(){printf("PWD = %s",getenv(PWD));return0;}
如果我们将mycmd拷贝到根目录,那么其就可以变成和pwd一样的功能。
因此,环境变量的意义就在于其可以找到指定文件的路径。环境变量就相当于系统启动时加载到内部的全局变量。
最后别忘了将拷贝的文件删掉。
4. 命令行参数
4.1 什么是命令行参数?
在开始之前,大家回想一下,有的编译器中的main函数中存在着这样的参数:
比如上面的DevC++中,main()中有着整形的argc参数和指针数组的argv参数,事实上,这两个参数这就是我们这一节中的主角:命令行参数。
那这个所谓的命令行参数到底是个什么鬼东西?有什么作用?能用来做什么呢?有的编译器怎么就没有呢?接下来开始演示:
1. 预备工作:
在演示之前,我们要对其进行准备工作,也就是设置一下Makefile以及程序的代码:
Makefile:
在这里,我们可以通过
$
脚本语言来改变之前的代码风格,即第一个
$
后的
@
代表生成的
mycmd
,第二个$后的^代表依赖项
mycmd.c
。而后面的std=c99就是我们演示此命令行参数的必须的指令,只有在c99环境下才能成功编译。
mycmd.c:
#include<stdio.h>#include<stdlib.h>intmain(int argc,char* argv[]){for(int i=0; i < argc; i++){printf("argv[%d]->%s\n",i, argv[i]);}return0;}
2. 演示命令行参数:
在程序中加上命令行参数之后,我们以打印的方式看看究竟它对应的是什么东西。执行:
我们可以发现,所打印的结果,就是对应的命令行中的命令以及选项,因此我们也知道了为什么叫做命令行参数:
但main函数是程序的入口,那么是谁调用了main函数呢?谁又将命令行参数传给了main函数呢?事实上这些都是shell和操作系统所做出的行为。因此有的编译器中没有并不是真的没有,而是隐藏在相关的位置,操作系统同样会通过命令行参数去通过命令调用这个程序。
4.2 命令行参数的作用
首先,对于我们上面所演示的两个命令行参数,具体有什么作用呢?那我们通过程序看一下:**[mycmd.c]**
#include<stdio.h>#include<stdlib.h>#include<string.h>// ./mycmd /-a /-b /-c// ./mycmd -ab/-ac/-bcintmain(int argc,char* argv[]){if(argc !=2){printf("Usage: \n\t%s [-a/-b/-c/-ab/-ac/-bc/-abc]\n", argv[0]);}if(strcmp("-a", argv[1])==0){printf("功能a\n");}if(strcmp("-b", argv[1])==0){printf("功能b\n");}if(strcmp("-c", argv[1])==0){printf("功能c\n");}if(strcmp("-ab", argv[1])==0){printf("功能ab\n");}if(strcmp("-bc", argv[1])==0){printf("功能bc\n");}return0;}
我们通过这样列出几个选项之后,我们就可以进行如下执行:
我们发现,即通过命令行参数的加入,我们可以通过一个程序的不同选项去实现他不同的功能,那么这就是命令行参数的作用。我们也就知道了对于ls一类的程序是如何通过选项从而去执行不同命令的。
事实上,对于这种【通过不同选项执行一个程序的不同功能】的方式,在windows也可以去执行,当我们通过
win+R
快捷方式打卡windows的终端,我们输入
shutdown /?
就可以看到shutdown不同选项所对应的不同功能:
我们随便用两种选项举例:1.
shutdown -s -t 360
: 电脑在360秒后自动关机:
- 通过
shutdown -a
终止这个程序发生,阻止6分钟后关机。(自己也可以动手试一下)
1. 第三个命令行参数env
当我们明白前两个命令行参数的作用时,事实上还有第三个命令行参数:
env
。我们知道,env命令能够查看环境变量,在命令行参数中,env也是指针数组类型,因为环境变量实际上也都是一个个的字符串,而最后一个字符串实际上就是NULL,也就是0,因此我们在编辑代码时可以用这个作为截止条件:
#include<stdio.h>#include<stdlib.h>#include<string.h>intmain(int argc,char* argv[]){for(int i=0; env[i]; i++){printf("env[%d]: %s\n", i, env[i]);}return0;}
那我们执行一下这个程序:
我们发现所有的环境变量都通过这个程序显示出来,这也就代表着env这个命令行参数确实将环境变量传入了程序中。
2. 验证命令行参数env
那为了验证这个env会将环境变量都传入到程序中,我们就新建一个环境变量
export myval=4444
,观察运行结果:
我们在众多环境变量中发现了myval。
3. 其他显示环境变量的方法
我们知道,除了通过命令行参数显示之外,我们也可以通过getenv获取,但是我们需要明白一件事情,env命令行参数这个指针数组是从哪里传入的环境变量呢?
事实上,env是通过C语言中第三方environ获取的,而environ本身作为二级指针保存,我们知道指针数组和二级指针可以通过解引用的方式相互转换,这也就恰恰对应了可以把environ中的内容一一传递给env,那么我们通过: man environ查看其需要的头文件并演示代码程序:
程序【
mycmd.c
】:
#include<stdio.h>#include<stdlib.h>#include<string.h>#include<unistd.h>intmain(){externchar** environ;//声明从文件中获取environ变量for(int i =0; environ[i]; i++){printf("environ[%d]: %s\n", i, environ[i]);}return0;}
结果:
我们同样也能通过这种方法获取到环境变量。
因此,我们知道在进程上下文中三种获取环境变量的方式:
getenv
函数获取、命令行参数
char* env[]
获取,第三方
extern char** environ
获取。其中推荐的是getenv获取,因为可以指定获取。
5. 总结环境变量
通过上面的描述,我们知道,环境变量就是操作系统启动之后将内部内容加载到内存中的变量,也就是全局变量。通过环境变量,我们可以对一些所需要的环境进行配置。
版权归原作者 每天都要进步呀~ 所有, 如有侵权,请联系我们删除。