前言
指令就是可执行程序,当我们运行自己的可执行程序时,需要用./来指定路径,可是为什么运行指令时不用指定路径呢?这就是环境变量的作用。
一.常见环境变量
环境变量是在程序运行期间需要用到的具有特定功能的一组变量,这些变量从配置文件中获取,存放于shell内部。说白了就是内存里的一些变量,里面存放了一堆字符串。
PATH
执行一个指令的前提是找到对应的可执行程序,执行一个指令时不需要带路径,就是因为系统会首先在默认搜索路径下查找,而这个默认搜索路径就在PATH这个环境变量中存着。要想查看一个环境变量,只需在变量名前加上$符号,然后echo(输出到屏幕)
可以看到,PATH里的内容是一个字符串,里面有很多路径,用冒号分隔,而我们的大多数指令就存放在其中的/usr/bin目录下。
所以要想自己的程序不带路径就能运行,有两种方法,第一是将程序拷贝到PATH中的某个路径下,例如/usr/bin;第二种方法是把可执行程序所在的路径添加到环境变量中去。
如图这就是修改环境变量的方法,直接给它赋值。由于我们是想添加内容,所以要$PATH后面加上我们想要添加的路径,然后要注意分隔符冒号。
PWD
PWD这个环境变量里存放的是当前所在路径,当我们使用pwd指令时,实际上就是读取了PWD的值。
HOME
每个用户都有自己的家目录,普通用户的家目录是/home/xxx,root的家目录是/root,家目录存放在存放在HOME这个环境变量中
为什么普通用户登录成功后,默认处于家目录/home/xxx,root登录成功后就处于/root呢?
登录的时候:
1.输入用户名和密码
2.认证
3.形成环境变量(不止一个,如PATH,PWD,HOME等)
根据用户名,初始化HOME=/root 或 /home/xxx
4.cd $HOME
二.查看环境变量
env
注意此时的USER
su
可以发现USER没有变化,并且当前所处的目录也不在root的家目录下 ,但是家目录确实已经变成/root了。
su -
可以发现,su-后USER变成root了,并且也处在家目录下。
总结:su只是把用户身份切换成root,而su -是让系统以root身份重新登录
三.在代码中调用函数获取环境变量
#include <stdlib.h>
char* getenv(char* name)
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main()
{
char* who = getenv("USER");
if (strcmp(who, "root") == 0)
{
printf("my command\n");
printf("my command\n");
printf("my command\n");
printf("my command\n");
}
else
{
printf("You are not root, you don't have power!!!\n");
}
return 0;
}
功能:拦截普通用户运行这个程序
四.命令行参数中获取环境变量
我们可能很少使用命令行参数,实际上执行运行程序时我们可以传递一些参数,这些参数能被main函数接收。main函数可以不带参,可以带两个参数,也可以带三个参数,第一个参数是int argc,接收参数的个数,第二个参数是char* argv[],接收参数数组,即字符指针数组。而第三个参数char* env[]接收的就是环境变量。
1.命令行参数表环境变量表
当shell登录时,系统会给shell创建一个环境变量表,也就是一个字符指针数组,每个指针指向一个环境变量。
系统启动程序的时候,可以选择给我们的进程(main函数)提供两张表:
1.命令行参数表:
2.环境变量表
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, char* argv[], char* env[])
{
for (int i = 0; env[i]; i++)
{
printf("pid: %d, env[%d]: %s\n", getpid(), i, env[i]);
}
return 0;
}
打印出来的信息和用使用env打印出来的信息是一模一样的。
2.命令行参数环境变量是由谁传递的
我们在命令行中启动的进程,实质上是shell(bash)的子进程,子进程的命令行参数和环境变量是父进程bash给我们传递的。
bash创建子进程,会获取我们输入的命令行参数,对它进行处理,然后传给子进程,这个好理解。但是,父进程的环境变量又从哪里来的呢?
我们将PATH清空,发现ls,env等命令就无法执行了,但是重新登录,发现指令又能执行了。
实际上,我们更改的是bash进程内部的环境变量信息。每一次登录,都会给我们形成新的bash进程,bash进程自动从某个地方读取信息形成自己的环境变量表信息。这个地方就是脚本配置文件,家目录下的.bash_profile文件。
发现其中还嵌套了.bashrc文件
继续套娃,/etc/bashrc
这里面的内容可就多了,有100行的内容,各种各样的环境变量信息。
五.手动添加环境变量
我们也可以手动向环境变量表中导入变量
添加一个变量MYENV,赋值为hello,再用env查看环境变量,发现MYENV并不存在。实际上我们创建的ENV叫做本地变量,并没有导入到环境变量表当中。要想将本地变量变成环境变量,只需export一下:
也可以在在定义变量时就导出到环境变量列表:
但是,当我们重新登录时,刚才自定义环境变量也就没有了。因为不管是本地变量还是环境变量,都是保存在bash进程中,是内存中的数据,bash退出,也就什么都没有了。所以如果我们想要定义一个环境变量,并且让它永久存在,就要更改配置文件。
当我们再登录时,新的bash就会读取脚本文件,形成环境变量。
六.环境变量的全局属性
bash的子进程会通过命令行参数的方式接收bash的环境变量,实际上,普通进程不用命令行参数的方式,也有办法将环境变量传下去。所以说,环境变量具有全局属性,可以父传子,子传孙,一直继承下去。
七.使用全局变量获取环境变量
前面说普通进程fork出来的进程也能继承父进程的环境变量,这是怎么做到的呢?
C语言库为进程提供了一个全局指针变量,char** environ,它指向环境变量表,这个变量定义在头文件<unistd>中。
不管是命令行参数还是环境变量,父进程bash都要先拿到,命令行参数和环境变量都是bash的数据。而bash创建进程也是通过fork创建的,而fork创建子进程,父子进程代码共享,数据以写时拷贝的方式各自私有一份,所以environ这个指针变量,每个进程都能访问到的,有了这个指针,也就能找到那张环境变量表了。
#include <stdio.h>
#include <unistd.h>
int main()
{
extern char** environ;//声明全局变量
for (int i = 0; environ[i]; i++)
{
printf("environ[%d]: %s\n", i, environ[i]);
}
return 0;
}
八.小结:获取环境变量的四种方式
1.env指令
环境变量列表是bash自己通过读取脚本配置文件创建的,所以在命令行,也就是bash内部,自然能够访问到环境变量列表
2.char* env[]接收命令行参数
bash可以以指针方式给子进程传递环境变量列表,main函数用变量env接收命令行参数,所以能够通过这个变量访问到环境变量列表
3.使用environ全局变量
C语言库提供了一个指向环境变量列表的全局指针变量environ,这个变量会被子进程继承下去,所以每个进程都会直接或则间接地继承environ这个全局变量,从而能够找到bash中的环境变量列表,访问环境变量
4.使用getenv函数
此函数用于访问特定的环境变量,参数传入你要获取的环境变量名字。
九. 本地变量和环境变量的区别
本地变量:本地变量只在bash内部有效,不会被子进程继承下去(不在环境变量表中)
环境变量:环境变量通过让所有子进程继承的方式,实现自身的全局属性
十.常规命令与内建命令
这里有一个问题,在bash中定义了一个本地变量a,前面说本地变量不具有全局属性,只在bash内部有效,子进程访问不到。但是echo是一条指令,它也是一个子进程呀,它怎么就能访问到a呢?
再看一个场景:
我们将PATH清空,ls,env这些指令就不能直接跑了,但是为什么echo指令不带路径还能执行呢?
这是因为echo和我们平常使用的命令不一样!!!
Linux的命令分为两类:
1.常规命令:磁盘上存在的可执行程序,shell(bash) fork创建子进程,让子进程执行任务
2.内建命令:shell(bash)的一个函数,不创建子进程,而是调用函数来执行任务
当bash识别到要执行echo命令,它会去调用自己内部的一个函数,这个函数名字就叫echo。由于这个这个函数是shell内部的,当然可以读取shell内部定义的本地变量喽,并且它也不需要什么默认搜索路径,直接调用内部的函数就好啦。
同样,export指令也是一个内建命令,因为如果让子进程去导入环境变量,那么环境变量不就到子进程的内存空间里去了。
当然,系统中的大多数指令都是常规命令,只有一些安全,短小的命令shell才会亲自执行。
十一.和环境变量有关的指令
1. echo: 显示某个变量值(本地变量和环境变量)
2. export: 导出一个新的环境变量
3. env: 显示所有环境变量
4. unset: 清除某个环境变量
5. set: 显示本地定义的shell变量和环境变量