揭秘环境变量:系统运行的隐形指挥官
环境变量
概念介绍
- 环境变量(environment variable)一般是指在操作系统中用来指定操作系统运行环境的一些参数(环境变量给bash用,bash用用户间接使用!)
例子:
我们在安装IDE时,可能会要配置环境变量
我们在VS编写代码的时候,我们并不知到我们的动静态库在什么位置!但是程序依旧可以正常运行,原因就是有相关环境变量帮助编译器进行查找
以上两点都是和环境变量有关系的!
我们安装python,但系统不只是安装python,还会安装一系列工具,所以需要环境变量把所有的工具都找到!这就是环境变量!
- 环境变量每个都不重复也都有不一样的功能,通常具有全局特性!
命令行参数
现在还需要了解命令行参数,这和之后要学习环境变量的存储格式有关系,所有现在先来了解一下命令行参数
我们在书写main函数的时候,我们平常是不写参数的,但main函数其实是有参数的,在编译阶段,编译器会做手脚,会加入参数int agrc , char * argv[],我们打印这个字符型指针数组来了解一下这个!


我们可以发现,argv是将bash中的命令行,以空格为单位存储在argv中!
其实我们可以发现,我们在命令行中输入一行指令(指令之间用空格隔开),实际上就是输入一条字符串,可以叫做命令行或命令行命令!
而我们在实际运行某个C语言程序时,这个命令行命令字符串会以空格为分隔符被切割开来,切成"./code"、"a"、"b"、"c"、"d"五个字符串,并且其地址被字符串指针数组储存起来,argc记录有效数据,argv数组有效数据放完,结尾一般会NULL结束
所有命令行指令被以空格为分隔符拆开的子串,就是命令行参数,这些命令行参数都被放在argv这个字符型指针数组中,有效元素由argc来指明
![]()
而我们平时用的命令,ls -a -b -c,file /usr/bin/ls,这些命令都是可执行程序,而这些命令大多都是用C语言写的!我们写的命令,后面还可以带很多选项,这个和命令行参数有什么关联呢?

我们做一个判断,判断argc是否为2!这个判断的意义就是,如果我们./这个可执行文件的时候,没用带abc这个三个选项其中一个,就要提醒用户需要正确书写命令
![]()
有了上面的基本认识接下来进一步认识


上面的例子是通过获取命令行输入的信息,程序通过argv来获取命令行参数,并通过不同命令行参数来实现不同功能!
我们之前写的命令,ls -l ,ls -b ,ls -a,这些命令的功能不同,所有我们可以知道命令行参数用途是为了让一个程序可以通过选项,来实现不同的子功能!
main的命令行参数,是实现程序不同子功能的方法!!!————指令选项的实现原理!
通过命令行参数,程序可以通过命令的拆解,来获取命令需要程序去做什么!那么是谁拆解了命令呢?是bash切分的命令,我们输入的命令字符串先是由bash获取的,bash获取字符串后切分并构建argv!
那我们之前为什么不用带命令行参数呢?因为我们的功能就一个!如果我们要程序由自己的子功能,就需要用选项了!
所以进程拥有一张表,argv表,用来支持选项功能!
以例子来认识一个环境变量
要执行一个程序,必须先找到它!!!(谁找?bash!!!通过PATH来找(环境变量))
我们在调用自己的程序时,需要./找到程序,那为什么我们写系统命令不需要找到命令的程序在那里呢?这是因为系统之中有环境变量!
系统中存在环境变量,来帮助系统找到目标二进制文件,ls指令的文件路径在 /urs/bin/ls中,ls可执行文件在/usr/bin文件之下,而我们自己的可执行文件在其他目录下直接调用,系统找不到这个文件在那里,如果输入一个指令,也就是可执行文件,系统会默认在/usr/bin中去查找这个文件,如果我们将code可执行文件,拷贝到/usr/bin/之下,code命令也可以直接调用!
但是我们并不建议将我们自己的可执行程序放入到默认的指令路径,这样可能会污染指令池
系统凭什么知道要去/usr/bin路径下去找呢?因为系统当中会有环境变量PATH(系统中搜索指令的默认搜索路径!)
通过指令
env来查看所以的环境变量!环境变量的构成是名字+内容
我们想查看具体的某个环境变量,要使用
echo $XXX即可
所以说,系统搜索指令,都是默认在上图中的文件路径去搜素的,所以说系统搜索指令,不是默认在/usr/bin下搜索指令,而是默认在PATH中的文件路径下去搜索指令,这些文件路径是以:为分隔符分开的
如何搜索,其实很简单,就是以:为分隔符,获取PATH路径下的文件路径,并以此做被搜索指令的前缀,搜素是否存在!存在就加载运行!
同理,如果我们不是将可执行程序拷贝到这些默认文件路径下,而是将可执行文件所在的工作目录路径添加到PATH下,也可以被默认搜索到!
通过指令环境变量 = 文件路径的方式可以添加文件路径到环境变量中!PATH = /home/zyx/BtyeNote/file.c/lesson_16!但是,我们添加这个路径进入PATH后,很多系统指令系统都搜索不到了!
此时我们查看PATH环境变量的路径,发现天塌了,我们上面的方法不是添加路径到环境变量,而是覆盖环境变量里的文件路径!如何恢复?PATH环境变量本身就是内存级的变量,就是我们看到的罕见病了PATH是bash内部new了一个变量,并从内存中拷贝了这个字符串到bash的PATH中,所以重启bash即可!
那要如何操作,才可以增加路径到环境变量中?指令
环境变量 = $环境变量:地址,即PATH = $PATH:/home/zyx/BtyeNote/file.c/lesson_16
问题:
1、如何理解环境变量呢? 存储角度
我们远程登录,系统就会创建一个bash进程,bash必须要从系统获取所有环境变量的信息,并在bash进程内部形成一张表!这个表叫做环境变量表(这个表就是一个指针数组char * [])每个环境变量是字符串,指针数组通过字符型指针指向这些字符串!
命令运行的全过程!
我们在bash窗口输入命令,bash接收到了命令并通过命令形成命令行参数表,之后再从命令中获取程序名,在环境变量表中找到PATH,再在PATH中,枚举每个地址+程序名,bash再在系统里找对应要执行的命令是否存在,若存在,就创建子进程并执行
所以bash内部有两张表(命令行参数表、环境变量表),环境变量就个长字符串,bash在启动时,会想办法在bash内部new一块空间,这块空间用于存放环境变量,对每个环境变量都new一块空间,然后将环境变量字符串依次拷贝到这个空间里面 ,bash在内部维护了这张表,所以env查表的时候就是直接打印了这张表了!
2、环境变量,最开始从哪里来的呢?
环境变量最开始是从系统的相关配置文件中来的! Linux系统是会存在相关的系统配置危文件的,这些配置文件当中,会包含所有环境变量直接或间接环境变量的内容,bash会在启动的时候,它会从配置文件中读取所有环境变量的值,并在自己的内部创建一张表,
配置文件在哪呢?
系统家目录~中的隐藏文件
.bashrc和.profile
.profile要求用户去加载.bashrc,.bashrc要求用户去加载/etc/bash.bashrc!
这里想要讲的是,登陆时bash进程会被创建,bash会读取
.bashrc和.profile配置文件来构建环境变量!那就有问题了!如果这样,那我们是不是可以把自己的路径想办法添加到配置文件里,这样每次bash创建都可以直接调用code可执行文件了?永久有效了?
我们直接在
~/.bashrc直接增加环境变量之后指令
source ~./bashrc,即可生效!这样就增加PATH环境变量下的默认路径,重新登录,bash也是通过配置文件
.bashrc获取最新的PATH
此后登录,在此路径下的文件就不需要带路径调用了!
所以这个案例告诉我们环境变量是从系统的配置文件过来的
登录时,bash会读取配置文件,读取配置文件形成环境变量表,从配置文件依次将环境变量写入环境变量表,bash还可以接收用户指令,所以还有第二张表命令行参数表,而这两张表的结构都是字符型指针数组,环境变量和命令行参数都是这数组所指向的字符串!
最后,如果Linux系统又10个用户登录呢? 那就存在10个bash!
所以我们在书写各种指令,输入命令行,bash会先解析命令,得到命令行参数表,得到程序名字,然后拿着程序名字,再去指定的环境变量PATH中进行搜索,若有带路径,就不会查PATH,若没用就需要查PATH 找到了就运行程序,没找到就输出no found such comment
认识更多的环境变量
HOME
指令who、whoami、cd ~,系统是怎么知道用户是谁?cd ~ 为什么会直接到家目录?
我们来认识一个环境变量HOME


用户登录,bash创建,在创建的时候,bash会创建环境变量表,而bash会根据用户名来创建家目录路径,并设置到HOME环境变量中,bash就把家目录当作用户的默认路径
所以我们在输入cd ~的时候,cd 命令会被bash获取,~也会被bash获取,cd命令bash会在PATH环境变量中查询,~bash就转换成HOME路径
所以HOME环境变量就是当前用户的家目录
SHELL
SHELL是保存bash路径,用于确定用户使用那个版本的bash

USER

记录当前用户是谁!LOGNAME是记录当前登录用户
我们用root登录,USER和LOGNAME都是root,而用普通用户,通过su切换超级用户,此时查发现USER和LOGNAME还是用户名,所以系统依旧认为用户不变,只是权限变了,而用su -就相当于用root重新登录
HISTSIZE
Linux的命令行中,可以通过上下键的方式来获取历史命令,bash会默认记录我们输入的命令,通过Ctrl+r或者上下键来查看查看历史命令,可以认为bash内部有一个队列,有命令进来即字符串,就把字符串入队列,但是这个存储命令的容器大小有限,bash只会存储最新的1000条命令,即最新的HISTSIZE(hist = history size大小)的命令
HOSTNAME

HOSTNAME记录着当前主机的主机名!
SSH_CLIENT
是记录我们是从那个客户端登录的,进程号,网络的端口号

SSH_TTY
是记录当前终端设备
LS_COLORS
LS_COLORS是存储配色方案

我们输入命令ls,bash输出信息的颜色就是看这个的
PWD
bash会记录当前我们的工作路径(即bash所在的路径)到PWD中!
OLDPWD
OLDPWD -> old_pwd,可以认识到,可能是存储旧工作目录的环境变量,我们来验证一下

而现在有一个指令,cd -功能就是回到上一个工作目录,怎么实现的?环境变量已经准备好了上一个工作目录的路径了!bash提前记录好了!

获取环境变量的方法
操作(查找,添加,删除)
export(envecho $XXX)unset
获取环境变量,可以直接env或者echo $XXX
那如果我们自己想导入一个环境变量要怎么做呢?
export MYENV = 1122334455
export相当于让bash往环境变量表中,新增一组环境变量,bash收到上面命令,就会把export后面的字符串导入到环境变量表中!
那我们要取消一个环境变量要怎么做呢?
unset MYENV
unset就是取消环境变量,会通过unset后面的字符串,去环境变量表中查找对应的环境变量名,若查到就删除这个字符串
代码查找(三个方法)
那我们想通过代码来获取环境变量要如何做呢?
我们之前讲解main函数最多有几个参数,现在可以回答了,最多有三个参数!是父进程传递给我们的
int main(int argc , char *argv[],char *env[]){}
这第三个参数env?env我们知道就是环境变量表,我们知道,main函数必然是子进程,是bash的子进程,而bash内部有命令行参数表和环境变量表,命令行参数就是bash为进程做的命令行解析,所以会传递给我们,而环境变量表在登录的时候就创建好了,也一起传递!
环境变量表虽然在内容上和命令行参数表不一样,但是其结构是完全一样的!这里三个参数,给argv带了参数,但是env没用带参数,要如何遍历env呢?
for(int i =0 ; env[i];i++){}我们知道命令行参数表的结尾是NULL结尾的,所以来看看,env能否遍历成功,main函数是否把父进程环境变量表拿了下来!

成功获取!
问题:main函数是如何做到的?main函数可以不带参数,也可以带参数,带两个参数,带三个参数的,这是如何实现的呢?
其实我们的可执行程序的入口并不是main函数,在Linux中入口函数是start函数,通过蛋哥的反汇编文件:
start函数内部会调用main函,即400410这个
这个start函数内部会维护
int arg_count=0;,在编译的时候,就会去查看main函数的参数,将参数的数量赋值到arg_count中,并做这样的操作
_start
{
int ret =0;
int arg_count = 0;
arg_count = 3l;
if(arg_count == 0)
ret = main();
else if(arg_count == 2)
ret = main(argc,argv);
else if(arg_count == 3)
ret = main(argc,argv,env);
}
方法1:获取环境变量,这个环境变量是父进程的(bash)
而我们自己向环境变量表中添加的环境变量也可以被子进程继承,即bash之后的所有子进程都可以继承环境变量,从二叉树来看就是bash根目录以下的进程都可以继承环境变量
这也证明了环境变量在系统当中通常具有全局特性
为什么要这么设计?
为什么要让子进程可以看到环境变量?接下来通过方法二讲解一起解决
方法2:getenv系统调用(setenv之后再用)

getenv的功能是获取一个环境变量的内容
getenv会根据环境变量的名字来获取环境变量的内容,getenv的头文件为#include <stdlib.h>,如果获取成功了,就会返回环境变量字符串的起始地址,否则就是null
我们用例子来展示以下

为什么这样写呢?
我们这些变量定义出来却不使用,编译器可能会警告,所以我们(void)强转一下,就是应用一下这些变量,取消报警

![]()
所以getenv也可以获取指定环境变量的内容!
如果我现在想实现一个程序,只用我一个人可以用,连root都不可以使用!
对于一个系统,只有一个东西认识当前用户是谁?——bash,bash在登录时从配置文件中获取用户名到环境变量USER中,可以以此来下功夫
上面这个程序就可以完成问题的要求,只允许root用户使用,通过bash(系统唯一知道用户的进程),而bash有会将环境变量表继承给子进程,所以我们的程序(子进程)可以使用getenv向环境变量表中查询USER环境变量(USER环境变量是存储登录用户信息的),用who字符串来接收getenv查询的结果,在程序中对who字符串进行一系列判断,即可完成身份识别的功能
这就是为什么要让子进程看到bash的环境变量,通过继承的方式,把环境变量的信息继承到子进程,子进程可以使用这些信息实现个性化功能!(环境变量实现进程级的信息传递,进而控制子进程的控制逻辑)
方法3:environ

environ为什么是二级指针?我们的环境变量表是 char env[],是一个存储char 指针的数组,environ为二级指针,说明environ二级指针是指向环境变量表的第一个元素

我们这个例子,不需要env命令行参数,在程序中,我们并没用定义environ变量,来看看能否正常输出环境变量表?

我们会发现编译报错,environ未被定义!我们确实没用定义,所以也要去定义一下!


所以environ也可以拿到环境变量
我们建议使用第二个方法获得某一个环境变量,而不是通过1,3方法获取环境变量表
理解环境变量的特性
环境变量具有全局特性
子进程都可以获取到环境变量相关数据

通过创建子进程,对子进程进行查环境变量表,也可以查找成功!
补充两个概念(给后面埋一个伏笔)(内建命令)
a、
在这里,i=10,叫做本地变量!
我们用env查查看,发现并没有i=10!这是因为i=10是本地变量不是环境变量,那我们要如何查看本地变量呢?
set命令可以查到bash的本地变量和环境变量(set命令还会记录shell的环境,脚本执行选项),bash会记录两套变量:1、环境 2、本地
所以什么是本地变量呢?
本地变量不会被bash的子进程继承,只会在bash内部被使用,为什么bash内部要用本地变量呢?
其实我们早就用过bash的本地变量了!bash本身其实也是一个解释器,bash的工作模式,除了一行一行的交互式工作模式,还有一种基于bash内部语法,如上面用法,为什么要有本地变量,就是为了让bash成为一种编程语言!可以定义变量不就可以是一个语言吗 ,bash是可以直接定义变量的,像python一样,本地变量就是为了支持bash解释某种脚本语言!
而有些本地变量是有特殊用法的!
这个PS1本地变量是什么呀?我们来看本地变量PS1的内容,会不会感觉有些眼熟,这个其实我们命令行提示符的格式,bash要记录好自己的命令行提示符格式,将来输出的时候就要按照这个格式输出的
那PS2本地变量呢?这个
>是什么呢?在bash有啥作用?
我们在输入命令行时,在命令末尾空格输入\,命令行会条到下一行并继续输入,所以是在做续行,>是bash应对续行时的命令行提示符!
所以本地变量很多是为了支持bash实现对于的功能的!
b、我们上面使用i变量作为例子,i变量是本地变量,那现在我想把本地变量i设置未环境变量,我们可以直接
export i即可,使用添加环境变量,不仅仅是export MYNEV = XXXX,还可以直接export 本地变量! 那么现在有个问题,export 添加环境变量,这个命令是如何把信息传输给环境变量表的?我们知道环境变量有全局性,会在bash的子进程中继承,但是export是命令是子进程,export要把接收的环境变量放到父进程bash的环境变量表中,那子进程要如何把数据传递会父进程呢?(进程之间不是讲究独立性吗?这里又没用继承关系)这里只有有个原因,这个export命令和其他的命令不一样,export这样的命令,我们称之为
内建命令 built-in command这个命令在执行时不需要创建子进程,而是让bash自己亲自执行
这么做到的呢?export命令,命令就是字符串,bash识别字符串,发现是export就调用bash内部的函数或者系统调用完成操作!
这也是为什么,我们当时把我们的PATH修改了,很多命令都无法使用了,但是有些命令 依旧还可以使用,比如pwd,cd,export,就是因为这些命令是内建命令,只要bash还可以跑,这些命令就可以跑
这里简单的讲讲:脚本!
我们在
XXX.sh后缀的文件中,写入一系列命令,如果我们想让文件里面输入的命令批量化执行,我们直接bash XXX.sh即可完成例:
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐













所有评论(0)