一、概念先知
首先我们在聊【环境变量】之前知道它是什么👇
环境变量(environment variables):一般是指在操作系统在开机的时候帮我们维护系统运行时的一些动态参数
- 读者可以回忆一下我们在学习
Java / Python
的时候,你们的老师是否有让你们配置过一个东西叫做【环境变量】呢?本文我们要讲的其实就是这个 - 打开【高级系统设置】中的【环境变量】,我们就可以看到熟悉的一幕了,如果有配置过Java中JDK的话一定会对这个有所印象
接下去我们要谈的是有关运行一个程序的方式
- 在 Windows 中,我们去运行一个程序是通过
双击
的形式 - 在 Linux 中,我们去运行一个程序是通过
./
的形式
- 那你是否想过在Linux中为什么我们不能直接通过敲入这个可执行程序的名字来运行这个程序呢?看到下面这二者的区别
- 可以看到,当我们去直接执行这个程序的时候,系统就报出了
command not found
。还记得我们在讲 Linux基本指令 的时候提到过【相对路径】和【绝对路径】,那对于./
来说呢,指的就是【相对路径】 - 那换句话说呢,既然我们可以使用【相对路径】来执行程序,也可以使用绝对路径去访问到这个程序 ,即执行
/home/pjl/linux/lesson13/myproc
这句指令即可。我们看到照样是可以正常运行的
💬 那有同学就诧异了,为什么一定要带上路径呢?为什么我直接运行就不行呢?
- 因为在我们的Linux系统中是存在一个东西叫做【环境变量】的,这个环境变量可以帮助我们在系统特定的路径下找到那些指定的程序,例如我们在使用指令
[ls]
的时候,也是有这个【环境变量】的缘故才得以使我们能够正常地去运行这个指令,因为对于[ls]
来说,你也可以将其看做是一个程序,它的存放路径是/usr/bin/
那看我一直在说这个环境变量,那它到底长什么样呢?我们来看看
- 如果要去查看这个环境变量的话,就需要使用到我们之前所学习过的一个指令叫做
echo
,其主要是用来【用于打印字符或者回显】,在其后面更上一个$PATH
,我们就可以去查询到当前系统中所有的环境变量 - 这个
$
呢可以看作是我们在C语言中学习过的指针,那时我们通过*
去对指针所指向的内容进行解引用的操作,在这里也可以这样去理解
echo $PATH
- 那我们运行来看一下,因为系统中的环境变量有很多,所以我们这里是以
[:]
来进行分割,刚才我们所执行的ls
指令就是在这个/usr/bin
这个路径下的
💬 所以我们就可以回答上面的那个问题了:为什么myproc不可以,ls却可以直接运行呢?
- 我们默认的程序,在系统中会存在一个环境变量,这个环境变量能够帮我们在系统中特定的路径下搜索这个特定的命令。因为当前的这个
myproc
程序并不在系统的环境变量目录下,==所以我们想要运行这个程序的,就需要手动地将这个程序添加到环境变量的目录下==
二、添加环境变量
那要怎么去将一个系统路径添加到【环境变量】中呢👈
- 这里要使用到一个关键字叫做
export
,然后将我们所要添加的路径放到其中即可
export PATH=/路径
- 在将这个路径添加到环境变量之后,我们再去直接执行这个程序,就可以发现它可以像正常的程序一样运行了。但是呢,当我在运行这一个个指令的时候,却发现它们都无法执行了,这时就有同学疑惑了,这是为什么?
- 那此时我们再去查看一下环境变量就可以发现,之前这里的很多系统路径都不见了,而是换成了我们之前所添加进去的这个路径。所以谜底就可以揭晓了,我们在往环境变量里添加路径的时候,对里面的内容造成了覆盖的情况,就像我们之前在讲 输入重定向 的时候,
>>
是追加操作,但>
却会造成覆盖的情况
- 所以我们在往环境变量里面添加路径的时候,不应该形成覆盖,这里我再介绍一种方法
export PATH=$PATH:/路径
- 那我们按照这个方法来运行的话再去查看环境变量就可以发现,这个路径被添加进去了,而且并没有覆盖掉其他的内容
- 接下去我们再去执行这个程序的时候可以正常运行了
- 但是呢,当我们再去将服务器重启之后,再度运行这个可执行程序的时候,便可以发现我们之前添加的路径不见了。那这就又造成了很多同学的困扰,这该
那此时我灵机一动💡,又想到了一个好的办法,那就是直接将这个路径拷贝到环境变量中
- 不过这个算是系统级别的操作,我们需要在【root】的权限下进行操作,普通用户的话带上
sudo
即可。然后我们看到在usr/bin
目录下就多出个myproc
,此时再去执行一下这个程序的话就可以发现可以正常运行
- 此时我们可以尝试再去一台服务器,发现这个可执行程序依旧是在的,说明这个方法是奏效的
- 既然可以拷贝过去的话也可以删除,但若是程序不在这个目录下的话就又无法运行咯~
三、通过代码如何获取环境变量
1、和环境变量相关的命令
首先我们来讲一讲和环境变量相关的命令有哪些
- echo: 显示某个环境变量值
- export: 设置一个新的环境变量
- env: 显示所有环境变量
- unset: 清除环境变量
- set: 显示本地定义的shell变量和环境变量
- 第一个
echo
、export
我们在上面讲到 添加环境变量 的时候就谈到了,接下去我们来讲讲这个env
。可以看到在敲出这个命令之后我们发现系统中所有的环境变量都显示出来了
- 这里可以带读者来简单地看两个,对于【HOSTNAME】而言指的就是 主机名,对于【SHELL】而言就是当我们在执行Linux指令的时候所需要交互的shell外壳,在这里是
bash
,最后的【USER】呢指的就是我们当前所登录的用户
2、测试HOME与USER
- 在这里我们可以来测试一下在不同用户之下所看到的环境变量有何不同,可以看到在普通用户下去查看
HOME
的时候出现的就是当前用户的家目录,如果是【root】的话出现的便是超级用户的目录;对于USER
来说也是同理
💬 所以环境变量呢是针对特定的人在特定的场景下被使用的,其会随着登录用户的不同而变化
3、环境变量的组织方式
相信有大部分的读者都学习过C语言,那么对于
main函数
和指针
一定有所了解
① envp表的介绍
💬 首先的话我想问一个问题:main函数的参数可以有几个呢?
- 那可能很多同学都不知道怎么回答,函数大家都有接触过,可以带的参数并没有多大的限制,但是对于
main
函数的话可能大家都是不带参数或者直接写个void
代表空参 - 那这里就要给读者普及一下了,对于 main函数 而言我们是可以携带【0】、【2】或【3】个参数的。即下面的三种形式
int main(void)
int main(int argc, char *argv[])
int main(int argc, char *argv[], char *envp[])
- 头两种形式大家或多或少见到过,不过本文我们重点要讲解的则是第三种形式,也就是这个带三个参数的,我们重点关注的是
envp[]
这个指针数组,如果对这一块知识点还不是很熟悉的同学可以去看看 指针入门到进阶全方位覆盖教程 - 对于这个envp,它里面所存放的都是一个个指针,这些指针都指向了一个个的字符串,即它的众多有效内容指向一个字符串,但是第一个无效内容必须去指向NULL,所以我们也可以称之为【表结构】
② 命令行第三个参数
在上面我们在讲到环境变量的时候提起使用
env
可以查看到当前系统中是所有【环境变量】
- 这里我们再度来观察一下可以发现,里面的所有环境变量所呈现的都是一种
[KV]
的结构,如果读者有学习过 STL中的map 的话就可以知道这是一种 键值对 的形式结构
- 经过上面这一番的学习我们可以利用 main函数 中的这第三个参数去获取到系统中的所有环境变量并且打印出来
int main(int argc, char *argv[], char *env[]) { // char *envp[]: 指针数组 for(int i = 0;; env[i]; i++) { printf("envp[%d]->%s\n", i, envp[i]); } return 0; }
- 去打印一下看看就可以发现,得到了我们想要的结果
③ 通过第三方变量environ获取
- 接下去再介绍一种方法,乃是通过一个第三方变量叫做
environ
来进行获取,下面我们通过【man】手册来查看一下这个第三方变量,发现其为一个二级指针,而且需要包含unistd.h
这个头文件
- 马上,我们来看一下代码该如何去进行书写
int main(int argc, char *argv[]) { extern char **environ; for(int i = 0;; environ[i]; i++) { printf("environ[%d]->%s\n", i, envp[i]); } return 0; }
- 然后我们来看执行结果可以发现打印出来的结果和利用第三个参数去获取环境变量是一样的
④ 通过函数获取
- 接下去第三种方法,乃是通过一个函数来获取,不过我们在这里就只是获取指定的那个环境变量,而不是全部的环境变量。我们依旧使用【man】手册来查看一下,这里也要注意包一下此头文件
stdlib.h
- 同样,我们来看看代码该如何去进行书写,这里的
USER
我们在上面看到过了,就是当前所登录的用户,所以若是其不为NULL
的话,我们就可以将其给打印出来
#include <stdio.h> #include <stdlib.h> int main(void) { char* user = getenv("USER"); if(user == NULL) perror("getenv fail"); else printf("USER: %s\n", user); return 0; }
- 那除了
USER
之外呢,我们还知道有一种环境变量叫做PWD
,就是获取当前所在路径
int main(void) { char* pwd = getenv("PWD"); if(pwd == NULL) perror("getenv fail"); else printf("%s\n", pwd); return 0; }
- 然后,我们再将这个可执行程序加入到【环境变量】中,然后再去任何的路径下执行,就发现其和
pwd
指令的效果是同样的了
好,接下去我们再来做一个提升,既然有【环境变量】这个东西,那我们就要利用好它,来做一个只允许指定用户来运行当前的程序
- 废话不多说,我们直接通过代码来看看,在这里我们涉及到名称的比较,所以我们需要使用到C语言中的
strcmp
,这里记得要包含头文件哦
#include <stdio.h> #include <string.h> #include <stdlib.h> #define NAME "pjl" int main(void) { char* own = getenv("USER"); if(own == NULL) perror("getenv fail"); else if(strcmp(own, NAME) == 0) printf("程序正常执行\n"); else printf("抱歉,当前用户 %s 没有权限执行此程序\n", own); return 0; }
- 那因为这里我们需要指定用户来运行,所以在这里我将切换为root,但是呢有一点很奇怪的是即使我们已经切换到了 root 的这个账户之下,但是为什么我在限定了只有
pjl
这个用户才可以访问之外,root
也可以来进行访问呢? - 右侧的话我其实已经给到提示了,问题出在这个
su
上,读者有必要知道一下它们的区别
su命令
:
- 仅切换用户身份,不改变环境变量。
- 环境变量继承原用户的环境,如HOME、SHELL等。
- 效果相当于以其他用户身份登录系统。
su - 命令
:
- 不仅切换用户身份,还改变环境变量。
- 环境变量从新登录该用户时的环境变量重新获取。
- 效果相当于重新登录系统。
- 我们可以通过查看
PATH
环境变量的方式来观察一下,于是就可以看到此还是pjl
用户所在的环境变量
因此经过上面的学习我们知道了要真正地去改变当前系统的环境变量时,尤其是我们在切换到 root 用户的时候,我们要使用到
su -
才可以起到真正的切换
- 我们来看看最后的执行结果可以发现,使用
su -
切换到 root 用户下的时候在执行这个可执行程序时,出现了不一样的结果,就是因为当前用户已经完完全全转为了 root,所以我们在进行比较的时候就进入了第三个分支
- 一样我们可以通过查看环境变量
PATH
和USER
来检测一下,发现确实发生了变化
最后再来补充一点,也就是我们上面谈到过的有关环境变量的命令
unset
- 首先可以看到,当我们设置了一个环境变量
myval
后,通过[echo]
的方式去进行打印,此时我们就看到了这个变量所对应的属性,接下去呢我通过unset
清除掉了这个环境变量,此时再去通过[echo]
的方式打印时,就看不到内容了 - 再来看一点,我又使用
export
将其放入环境变量表中,此时我们通过[env]
配合[grep]
指令去显示所有环境变量的时候,就看到这个变量通过键值对的形式进行展现