一、通俗例子解释环境变量概念与作用
想象你在厨房做饭,需要找到各种调料和工具。这些调料和工具就相当于计算机中的“资源”,而环境变量就像厨房里的一本规则书,里面列出了厨房里所有调料和工具的位置。
- 具有全局性(所有人都能用):就像这份规则书对家里所有成员都是可用的一样,全局环境变量在整个系统内都是可见和可用的。类似地,什么用户在使用计算机,都可以使用同一份环境变量来找到所需的信息。
- 记录查找的固定路径:规则书中记录了盐、糖、油等常用调料的固定存放位置,全局环境变量也定义了操作系统查找常用工具和资源的标准位置。类似地,系统环境变量
PATH
指定了操作系统查找可执行文件的标准路径,用户可以通过程序名快速启动该程序。 - 易于维护:如果规则书中的某项信息发生了变化,只需要更新规则书,家里所有人都会知道新的位置。同样,更改全局环境变量后,整个系统内的所有程序都会使用更新后的信息。
- 初始设置(配置通用):当家庭成员第一次使用厨房时,这份规则书可以帮他快速知道必要的操作知识。类似地,当一个新的用户登录系统,或者一个新的进程启动时,全局环境变量提供了必要的初始配置。
通过这个例子,我们可以看出全局环境变量就像是一个通用的规则集,它在整个操作系统中为所有用户和进程提供了统一的配置信息。
二、Linux中常见的环境变量
PATH
- 描述:指定系统搜索可执行文件的目录路径。
- 示例:
/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games
- 作用:当用户在命令行输入一个命令时,系统会在
PATH
变量指定的目录中查找相应的可执行文件。
HOME
- 描述:用户的主目录路径。
- 示例:
/home/username
- 作用:许多程序会根据
HOME
变量确定用户的主目录位置。
LANG
- 描述:语言环境设置。
- 示例:
en_US.UTF-8
- 作用:控制程序使用的语言和字符集。
SHELL
- 描述:用户的默认Shell。
- 示例:
/bin/bash
- 作用:指示用户的默认Shell类型。
USER
或LOGNAME
- 描述:当前登录用户的用户名。
- 示例:
username
- 作用:许多程序会根据此变量来确定当前用户的用户名。
- 这两个环境变量其实有本质区别:【Linux】Linux 环境变量中 LOGNAME 和 USER 有什么本质区别
PWD
- 描述:当前工作目录。
- 示例:
/home/username
- 作用:表示当前用户所在的工作目录。
OLDPWD
- 描述:上一次的工作目录。
- 命令
cd -
:可以跳转到上一次的工作路径,这条命令底层就是通过获取环境变量OLDPWD
实现的
我们下面通过认识一个很常见的环境变量 PATH
来进入我们的环境变量的深入学习 :
三、谈谈环境变量 PATH
我们在使用 Linux
系统时,其实已经使用过该环境变量,例如使用系统命令如 ls
、touch
……
这些命令本质上都是在系统目录 /usr/bin/
下的可执行文件,而你需要给系统提供某个可执行文件的全路径,系统会根据路径在对应目录下找到并执行该可执行文件。
在Linux系统中,用户可以仅输入命令名称而不是完整路径来执行该命令的可执行程序,这是因为系统会在环境变量 PATH
定义的目录中查找可执行文件。当你输入一个命令时,shell
会检查这个变量中列出的每一个目录,看看其中是否包含一个与你输入的名字匹配的可执行文件。
环境变量PATH
包含了多个目录路径,这些路径之间通常用冒号(:)分隔。当一个命令被执行时,Linux系统会依次搜索这些目录中的文件。如果在任何一个目录中找到了匹配的可执行文件,那么这个命令就会被执行。
常见的目录包括/bin
、/usr/bin
、/usr/local/bin
等,这些目录包含了操作系统提供的许多基本命令。
例如:我查看我的 Linux
系统中的环境变量 PATH
如下
比如你输入ls
命令,系统会在 $PATH
指定的所有目录下查找名为ls
的文件。
如果希望执行不在$PATH
中的程序或者想要执行某个目录下的特定程序,可以使用绝对路径或相对路径来指定程序的完整路径,例如/usr/local/myapp/mycommand
。
1、执行自己的程序而不带路径的两种方式:
- 1、将你的程序拷贝到当前环境变量
PATH
中存储的任一路径目录底下 - 2、直接将自己程序的全路径,添加到环境变量
PATH
中
(1)命令 pwd
命令 pwd
:查看当前你的可执行程序所处路径
(2)命令 echo $PATH
命令 echo $PATH
:查看当前环境变量 PATH
内容,并手动复制该值(你修改新的 PATH
需要把别人之前的值一起保留)
(3)手动添加自己路径
手动将你的程序路径添加该环境变量 PATH
字符串后面:以冒号作为分隔符
原PATH /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
我要添加的路径 /home/mine/linux-learning
两者合并 /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/home/mine/linux-learning
(4)命令 export PATH=变量值
命令 export PATH=变量值
:将新的环境变量 PATH
添加到环境变量表中,更新环境变量 PATH
最后可以命令 echo $PATH
检查一下是否修改成功
(5)检验结果
声明:我在当前路径下创建一个可执行文件 test1
,用于打印 hello world
。
- 第一次命令
test1
:只需要该可执行程序名,系统会在环境变量PATH
的目录中找到并执行我们的 可执行文件test1
- 第二次命令
./test1
:这次则提供了该可执行文件的路径,系统直接通过该路径就能找到该可执行程序的位置并执行
2、修改环境变量会永久保存吗?
环境变量,开始都是在系统的配置文件中的!
shell
进程的环境变量的由来:我们登陆系统 => 启动一个shell进程 => 读取用户和系统相关的环境变量的配置文件 => 形成自己的环境变量表 => 会继承给子进程
因为我们每次启动一个shell进程都会读取系统的配置文件来进行初始配置,因此我们刚刚自行修改了环境变量 PATH
只是对此次启动的 shell进程的环境变量做修改,而并不会永久保留下来,退出该 shell
进程,所做的修改就会消失,下次启动新的 shell
进程,则又是读取系统文件来配置,由此也可以得出一个方法:若想要永久修改系统的环境变量,可以手动添加或修改系统的相关配置文件。
四、环境变量的组织方式
每个程序都会收到一张环境变量表,环境变量表是一个字符指针数组,每个指针指向一个以’\0’结尾的环境变量字符串
用于将本进程的所有环境变量集中存储在一个字符串数组,最后以 NULL
结尾。
五、查看环境变量(五种方法)
通过命令查看
(1)命令 env
**命令 env
:**查看当前进程的环境变量表,会打印出当前shell环境下的所有环境变量及其值,以 variable=value
的形式展示。
(2)命令 echo $env_name
命令 echo $env_name
:查看名为 env_name
的环境变量
例如我指定查看环境变量 USER
的值(上面这张图的最后一行那个)
通过代码查看
(3)命令行第三个参数
命令行第三个参数:main
函数的第三个参数环境变量表 envp
在 main
函数中,存在三个函数参数:int main(int argc, char *argv[], char *envp[]);
,参数 char *envp[]
就是用于接收父进程传递的环境变量表。
(若想要具体了解一下 main
的各个参数具体从何而来,可以看看此篇博客:【Linux】main函数的参数列表从何而来?-CSDN博客)
代码演示
因为环境变量表 envp
最后一个元素是 NULL
因此可以直接通过这个作为遍历环境变量表的结束判断
#include<stdio.h>
int main(int argc, char *argv[], char *envp[])
{
for(int i = 0; envp[i]; ++i){
printf("envp[%d] : %s\n", i, envp[i]);
}
return 0;
}
如下图,将该程序 main
函数接收到的环境变量表打印出来
(4)通过第三方变量environ获取
C语言标准库中定义的全局变量 environ
是一个字符串数组指针,指向的是当前进程的(虚拟)内存空间中全局数据区存储的那个环境变量表,就是子进程继承于父进程的那个环境变量表。(这里暂时不讨论为什么要加一个“虚拟”的修饰)
environ
没有包含在任何头文件中,所以在使用时 要用 extern
声明。
代码演示
#include <stdio.h>
extern char **environ;
int main()
{
for(int i = 0; environ[i]; i++){
printf("%s\n", environ[i]);
}
return 0;
}
如下图,将该程序将全局变量 environ
指向的环境变量表打印出来
通过系统调用获取或设置
(5)函数 getenv(char* env_name)
getenv
函数在C语言中用于检索当前进程的环境变量。它是一个标准库函数,用于获取指定环境变量的值,使用需要的头文件为 <stdlib.h>
。
getenv
函数通常在程序中用来访问环境变量,而不需要显式地处理环境变量表。
代码演示
打印环境变量 PATH
#include <stdio.h>
#include <stdlib.h>
int main()
{
printf("%s\n", getenv("PATH"));
return 0;
}
运行结果如下
(6)函数 putenv(char *string)
:更改或设置环境变量
putenv
函数在C语言中用于修改环境变量的值。它允许您设置或修改当前进程的环境变量,并且这些修改会影响到后续创建的子进程。putenv
函数通常用于在程序中更改环境变量的值。
1、如果环境变量已存在:putenv
会修改现有环境变量的值。
2、如果环境变量不存在:putenv
会创建一个新的环境变量,并将其添加到环境变量表中。
代码演示
注意:该函数是修改当前进程的环境变量表,不会对父进程造成影响,因此运行一个下面代码的可执行文件,本质修改的是这个运行的子进程的环境变量表,因此需要前后将环境变量表打印出来看看
#include <stdio.h>
#include <stdlib.h>
extern char **environ;
int main()
{
// 修改前
for(int i = 0; environ[i]; i++){
printf("environ[%d] : %s\n", i, environ[i]);
}
putenv("Myenv=hello_Linux!!!!");
// 分割线
printf("\n——————————————————————\n\n");
// 修改后
for(int i = 0; environ[i]; i++){
printf("environ[%d] : %s\n", i, environ[i]);
}
return 0;
}
运行结果如下
为了观感,我把部分没必要的部分删掉了
六、与环境变量相关的命令
这两个本文前面讲解过了,这里不赘诉:
echo: 显示某个环境变量值
env: 显示所有环境变量
(1)export
: 设置一个新的环境变量
该命令可以向当前进程的环境变量表中导入一个环境变量
1、如果变量不存在:当环境变量表中不存在你导入的变量,则将该新环境变量添加进来
2、如果变量已经存在:当环境变量表中已经存在你导入的变量,则会对表中的旧值更新成你新导入的
3、如果导入的变量没有赋值,同时变量不存在:当你要导入的环境变量没有赋值,则默认值为空,也会导入到表中
4、如果导入的变量没有赋值,同时变量已经存在:即没有指定新的值,那么原表中变量的值将保持不变(不会被空值影响)
运行结果如下:
(2)unset: 清除环境变量
使用格式:指定环境变量名即可
unset MY_VARIABLE
echo $MY_VARIABLE # 输出: (不会输出任何东西,因为变量已被删除)
注意,unset
命令会完全移除环境变量
运行结果如下:移除上一张图中添加的环境变量(本来是在环境变量 LESSCLOSE
前面的)
(3)set: 显示本地定义的 shell 变量和环境变量
命令 set
不区分环境变量和其他类型的变量(如本地变量),一律打印
运行结果如下:
七、本地变量
本小段内容用于补充:
概念
在Linux系统中,Shell环境中有两种类型的变量:本地变量(local variables)和环境变量(environment variables)。本地变量只在当前Shell会话中有效,不会传递给任何子进程,而环境变量不仅在当前Shell会话中有效,还会传递给子进程。
本地变量:相当于程序内的局部变量,只能被局部域使用(只在当前Shell会话中有效)
环境变量:相当于程序内的全局变量,可以被全局任何地方使用(全局有效,可以传给子进程一起用)
由于本地变量的局部性,因此在该 bash
进程关闭后,本地变量也就没了,重新启动一个新 bash
进程不会看到之前创建的本地变量。
如果你想要添加 “永久性的本地变量”,就需要修改添加到系统的相关配置文件中,因为一个 bash
进程启动前会读取系统的配置文件,配置一些初始变量及其他数据。
创建本地变量:
输入 variable=value
就能创建成功,通过命令 echo $变量名
就能查看目标本地变量的值
将本地变量变成环境变量
//定义一个本地变量
a=10
// 导成环境变量
export a
验证是否导入:env打印环境变量出来看
八、添加变量的本质
添加环境变量的本质:其实就是在进程中 malloc
一块空间,保存该变量
过程如下:
1、分配内存:为新的环境变量字符串分配内存。
2、更新环境变量表:在环境变量表中找到合适的位置插入新的环境变量字符串。
3、更新环境变量表的终止符:确保环境变量表以NULL终止
在系统中添加本地变量也是一个道理,都是先分配内存,然后保存该本地变量。
九、环境变量的传递过程(重要)
继承环境变量:
- 当父进程通过
fork()
创建子进程时,子进程会继承父进程的环境变量列表。这意味着子进程在创建时已经有了与父进程相同的环境变量列表。 - 这一过程是通过
fork()
函数自动完成的,不需要额外的操作。
执行新程序:
子进程创建完成后,通常父进程会调用exec
系列的函数替换这个创建出来的子进程,用于执行新的程序。
- 如果父进程没有修改自己的环境变量表,父进程通过
exec
函数传递给子进程这张未修改过的 “旧表”。(本质还是该子进程创建时继承自父进程的环境变量列表) - 如果父进程修改了自己的环境变量表,父进程通过
exec
函数传递给子进程这张修改过的 “新表”。
如果父进程执行 fork + exec
的过程创建子进程,该子进程实际上会接收两次环境变量,第一次子进程接受的环境变量表是 fork
函数默认拷贝,第二次是 exec
函数直接传递的,不过最后子进程的环境变量表是 exec
函数传递的那一份(也就是最新的一份)。
澄清:子进程在通过 fork
函数创建时已经有了与父进程相同的环境变量列表,相当于子进程的本体就有。因此只是看上去子进程接收了两次。
环境变量可以被子进程继承,这里证明了环境变量的一个大作用:进程具有独立性,进程间信息互不干扰,但是可以通过环境变量完成进程间信息的传递!!!
十、为什么环境变量具有全局性?
点击跳转本篇博客,可得到更加详细的讲解:【Linux】为什么环境变量具有全局性?写时拷贝优化?-CSDN博客