作者:阿润菜菜
专栏:Linux系统编程
我们平常所用的Linux指令其实也是可执行程序,和我们自己写的二进制程序没什么两样,那么为什么我们在执行自己的程序的时候需要加上 ./,而在我们执行系统提供的指令时(可执行程序),为什么不需要加上./ 呢?
其实时我们要执行一个程序或者指令,必须先找到这个程序。
所以我们在执行自己写的程序时,要加上./(当前路径),其实就是为了找到我们所写的程序,而系统指令实际上是系统默认帮我们找到其程序的位置并且执行,如果想不带./的执行自己的程序,那就需要将程序安装(安装的本质就是拷贝)到/usr/bin目录下面,也就是系统安装指令的路径当中。但我们不建议这么做,因为未知的程序可能会有污染系统指令池的风险。
所以什么是环境变量?
概念: 环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数
就是说,比如我们在执行C/C++代码代码时,会调用语言的库就,那在链接的时候,我们从来不知道我们所需要链接的动静态库在哪里,OS照样可以链接成功,生成可执行程序,原因就是有 相关 环境变量帮助编译器去进行查找。
像是ls这样的指令程序就是在/usr/bin目录下的,那为什么在/usr/bin路径下的程序,系统就可以找到呢?其实是因为系统里面存在环境变量PATH,操作系统在启动的时候,会在shell的上下文当中定义一个PATH变量,这个变量是全局有效的,如果想要查看内容,可以利用echo $PATH命令进行查看。
查看环境变量的方法: echo $NAME //NAME:你的环境变量名称
在执行系统指令的时候,系统会默认去PATH环境变量里面的路径,查找我们输入的指令程序,如果找到,系统就会执行这个程序,如果没有找到就会报错command not found,如果我们想要想要像:ls、pwd、tree等命令一样使用起来不带路径的话,我么只需要将我们的可执行程序添加到PTAH中任一一条目录下或者将我们的路径追加到PATH环境变量下面;
另外我们可以任意修改PATH的值,因为只要重新退出登录,PATH环境变量就又会恢复了
我们可以用export指令来将shell变量导入到环境变量PATH里面,导入的时候需要先将老的环境变量导入进去然后在加上新的路径(追加路径),否则会出现你的路径直接覆盖掉之前环境变量PATH里面的所有路径的情况,PATH里面的路径下的所有程序都被默认为是系统指令。
PATH=$PATH:路径
像这样,过后我们不仅可以不带路径的使用我们追加的路径下的可执行程序,同时也不会影响ls等命令的使用。
另外我们的环境变量是内存级的,所以在你将自己的路径导入到环境变量PATH之后也只是暂时的,等你退出xshell之后,你的环境变量就又会恢复到默认的样子了
下面我们来看一些常见的环境变量:
PATH : 指定命令的搜索路径;
HOME : 指定用户的主工作目录(即用户登陆到Linux系统中时,默认的目录)
HOME环境变量记录当前用户的工作目录在具体的哪个路径
cd的本质其实就是shell在解析指令时,看到了波浪号,shell就会直接调用环境变量HOME的值
SHELL : 当前Shell,它的值通常是/bin/bash
当然这都是一些比较常见的环境变量,我们如果想要查看所有环境变量的话,我们可以使用 命令env
无论是我们自己写的程序还是操作系统提前给我们准备好的程序,想要运行都必须先加载到内存里面,因为CPU只能从内存中读取代码和数据,但是这里有一个潜在的问题,这些程序想要运行,都必须让操作系统先找到这些程序,找到这些程序才能把他们加载到内存里面.操作系统要找这些程序就必须去特定的路径下面去找这些程序,包括系统自带的指令程序和我们自己所写的程序,操作系统还有可能找头文件,找动态库静态库等等,但是为什么你说操作系统能找到这些东西,它就能一定找到呢?操作系统是如何找到这些东西的?
其实想要找到这些东西,操作系统需要做很多的准备工作,操作系统在启动的时候,他就已经默认从配置文件当中读取了他自己曾经把软件安装到了哪些路径下,他把安装到哪些路径下这些重要信息都记录在配置文件里面,等到OS启动的时候,把配置文件中的这些信息导入到内存里面,构建出一个内存级变量,这种变量就是环境变量,上面所讲的PATH环境变量就是操作系统在启动命令行解释器shell的时候,将PATH这样的变量导入到shell的上下文当中,当我们执行对应的指令的时候,我们就必须通过PATH环境变量里面指定的默认的搜索路径去查找对应的可执行程序,所以操作系统为了让我们找到可执行程序,其实做了很多的准备工作,帮我们定义了许许多多的环境变量,通过环境变量帮助我们做了很多本身我们看不见的工作,除PATH这个环境变量,其实还有很多很多的其他环境变量,操作系统需要完成其他很多我们忽略掉的工作,这就需要依靠这些它自己定义出来的环境变量,这些环境变量都有不同的用途,配色方案,当前路径,主机名,用户名,历史指令记录,默认的shell类型这些都要依靠OS他自己定义出来的环境变量去隐式的解决或处理我们看不到的问题和工作。
在不同的使用场景下,要求操作系统在启动shell之后,给我们做命令行解释的时候,必须预先设置好一批未来shell可能用到的变量,通过这些变量完成我们输入的命令的解释,所以 操作系统为了满足不同的应用场景,必须预先在自己的OS内设置一大批的全局变量,这些全局变量其实就是环境变量!
首先我们知道环境变量是存储的有内容的,那么这些环境变量里面的内容是从哪里来的?
答:环境变量的内容一般是由os从特定的配置文件中读取到的,当我们的os启动完毕过后,os就会读取相关配置文件里面的数据放到环境变量里面;在Linux下环境变量一般都在家目录下的.bashrc和.bash_profile;
这些环境变量也是数据,os也会对这些数据进行管理,os利用一个字符指针数组来维护这个环境变量表
每个程序都会收到一张环境表,环境表是一个字符指针数组,每个指针指向一个以’\0’结尾的环境字符串;这个环境变量表的最后一个元素是NULL,以此来表示环境表的长度
环境变量通常具有全局属性,可以被子进程继承下去
#include
#include
int main()
{
char * env = getenv("MYENV");
if(env){
printf("%s\n", env);
}
return 0;
}
直接查看,发现没有结果,说明该环境变量根本不存在.
然后我们导入环境变量
export MYENV="Hello World"
再次运行程序,发现结果有了!说明:环境变量是可以被子进程继承下去的!
这是为什么?
通过代码环境变量主要有3种方式:
1.通过main函数的参数获取:
我们都知道main函数是可以带参数的,而且最多可以带3个参数,比如:
int main(int argc,char*argv[],char*envp[]);
其中envp就是指向这个环境表的首元素指针,通过envp我们可以获取到环境变量:
#include
int main(int argc,char*argv[],char*envp[])
{
int i=0;
for(;envp[i];i++)
{
printf("envp[%d]--->%s\n",i,envp[i]);
}
return 0;
}
测试结果:
2.我们也可以不用main的参数,C语言给我们提供了一个全局的字符指针,用来指向环境表,我们也可以通过这个全局的环境表来访问环境变量,这个全局的字符指针也就是:environ,我们只需要声明一下即可使用:
#include
extern char** environ;
int main()
{
int i=0;
for(;environ[i];i++)
{
printf("environ[%d]--->%s\n",i,environ[i]);
}
return 0;
}
#include
#include
int main()
{
printf("%s\n", getenv("USER"));
printf("%s\n", getenv("PATH"));
return 0;
}
利用该函数我们可以搞一个只允许私人(如IKun)使用的pwd命令!
#include
#include
#include
#include
#define NAME "Ikun"
void Mypwd()
{
char* pwd = getenv("PWD");
assert(pwd);
printf("%s\n", pwd);
}
int main()
{
char* user = getenv("USER");
assert(user);
if (strcmp(NAME, user) == 0)
{
Mypwd();
}
else
{
printf("\n小黑子...\n");
}
return 0;
}
为了保持我们的pwd指令能像系统的pwd指令那样执行,我们需要将我们的代码所形成的可执行程序添加到PATH其中一个默认目录下:
测试:
显示本地变量和环境变量的指令:
导入环境变量和取消环境变量
export MYVAL="youcanseeme"
unset MYVAL
由于自己定义的环境变量默认就是字符串,所以在定义的时候既可以带上双引号,也可以不带双引号,但如果出现定义的环境变量带空格的话,就必须带上双引号了,所以还是建议在定义的时候带上空格。
1.父进程shell定义的本地变量不会被子进程继承下去,但是父进程的环境变量是会被子进程继承下去的,继承的原因就是为了满足不同的应用场景,因为许多系统指令(ls、whoami、pwd、which、su - )都会涉及到使用环境变量,所以这些指令(子进程)必须继承父进程bash的环境变量,以满足不同的使用场景。
所以环境变量是具有全局性的,因为无论是父进程还是子进程都有环境变量,子进程的环境变量是从父进程继承得来的。
2.本地变量只会在当前进程bash内部有效,因为它不会被继承下去,具有局部性。
上面我们再介绍利用代码获取环境变量的时候,提到了可以利用main函数的参数来获取,其中我们介绍了envp参数,但是argc、argv参数是什么呢,现在我们就来理解一下这两个参数的意义:
从结构上我们可以看到argv与envp是同一个类型,于是我们可以大胆的猜测一下,argv也可能表示的是一张表!其中argc是这个表的长度!
打印出来看看:
#include
int main(int argc, char* argv[], char* envp[])
{
int i = 0;
for (i=0; i < argc; i++)
{
printf("argv[%d]---> %s\n", i, argv[i]);
}
return 0;
}
1. main函数中的第一个参数是命令行中运行程序的时候字符串的个数,以空格为分隔符,比如上面运行时-a -b -c等,实际上是三个字符串,./mycmd也是一个字符串,所以argc代表的就是字符串的个数,argv指针数组中的指针,指向的就是这些字符串,通过程序运行结果和代码,可以证明这个结论,argv数组中打印出来的值实际上就是这些字符串,所以main函数中的第二个参数就是命令行参数表,表中的指针指向命令行中的所有字符串
2.所以你看这和我们ls -a -l 的形式是不是一样的,实际上我们在命令行指令+选项的方式时,shell会读取到这一串字符串,然后以空格作为间隔,将指令和选项存在一张表里面,当Shell为我们的指令创建进程的时候就会将这张表传给子进程!子进程就可以根据选项表现出不同的功能;
3. 系统指令其实就是C语言写的程序,那么它在带指令运行的时候,也能实现不同的功能,这是怎么做到的呢?实际上在实现的代码中的main函数就是需要argc、argv这样的参数实现的
4.所以命令行参数最大的意义就是,通过不同的参数(也就是执行时携带的选项)使得进程拥有不同的功能。
类似的在windows下的命令提示符当中,我们也可以通过不同的命令行参数,来使得进程实现不同的功能,例如下面的关机指令,可以设置关机时间,也可以取消关机,选择关机,通过-t、-a、-s等参数实现。