谈谈环境变量
为什么我们在终端输入 ls
,系统就知道要去哪里找这个命令?为什么我们编译代码时,编译器总能找到那些“藏”在系统各处的库文件?这一切,都要归功于一个低调而强大的存在——环境变量。
一、基本概念
- 环境变量 , 一般是指在操作系统中用来执行操作系统运行环境的一些参数
- 如 : 我们在C/C++代码的时候,在链接的时候,从来不知道我们所连接的动态静态库在哪里,但是同样可以链接成功,生成可执行程序,原因就是有相关变量帮助编译器进行查找
- 环境变量通常由某些特殊用途,还有在系统中通常具有全局特性!!!
以前学编程语言的时候,我们说main其实是程序的入口,但是我们在编写的时候,往往会不写两个参数,其实main函数也是由其他函数调用来的。
char* [] : 要么指向字符地址 , 要么指向字符串地址 , 到底是啥呢?
1.1 命令行参数表
我们新建一个code.c文件进行验证,查看一下 argv 这个指针数组 以及argc这个变量到底存储了啥!!!!
我们不难发现,这个命令被分割了,并且填入到argv这个指针数组里,同时argc会记录有多少个字串!!!!!!有啥用,为啥要放在这里,谁需要???
1) 先搞懂 argc
和 argv
是啥
当你在终端里输入一个命令(比如 ls -a -b -c
)并执行时,系统会把这个命令拆分成一个个 “小片段”,然后交给程序的 main
函数。
argc
:是 “argument count” 的缩写,意思是参数的数量。比如ls -a -b -c
,这里有ls
、-a
、-b
、-c
这 4 个部分,所以argc
的值就是 4。argv
:是 “argument vector” 的缩写,它是一个指针数组,里面每个元素都是一个字符串,依次存放着拆分后的命令片段。对于ls -a -b -c
,argv[0]
就是"ls"
,argv[1]
是"-a"
,argv[2]
是"-b"
,argv[3]
是"-c"
。
2)有啥用?为啥要放在这里?
这就得从程序如何根据用户输入执行不同操作来说了。程序自己没法 “主动” 知道你想让它干啥,得靠你在命令行里告诉它。argc
和 argv
就是程序接收你 “指令” 的通道。
- 以
ls
命令为例,ls
本身能列出目录内容,但加上-a
就会显示隐藏文件,加上-l
会显示详细信息。ls
程序内部是怎么知道你加了-a
还是-l
呢?就是通过argv
里的内容来判断的。 - 程序开发者在写
ls
的代码时,会去检查argv
里的元素。比如看到argv
里有"-a"
,就执行 “显示隐藏文件” 的逻辑;看到有"-l"
,就执行 “显示详细信息” 的逻辑。而argc
能告诉程序一共接收到了多少个参数,方便程序去遍历argv
数组里的内容。
3)谁需要?
编写需要 根据命令行参数执行不同功能的程序的开发者 需要它们。
举个简单的例子,你自己写一个小程序 mycmd
,希望它能根据不同参数做不同事:输入 mycmd -h
显示帮助信息,输入 mycmd -v
显示版本信息。这时候你就得在 main
函数里通过 argc
和 argv
来获取用户输入的参数,然后编写对应的逻辑。
比如下面这个简单的 C 程序:
#include <stdio.h>
int main(int argc, char *argv[]) {if (argc > 1) {if (strcmp(argv[1], "-h") == 0) {printf("这是帮助信息~\n");} else if (strcmp(argv[1], "-v") == 0) {printf("版本 1.0\n");} else {printf("未知参数:%s\n", argv[1]);}} else {printf("请输入参数,比如 -h 或 -v\n");}return 0;
}
编译运行后,输入 ./mycmd -h
,程序就会通过 argv[1]
拿到 "-h"
,然后执行显示帮助信息的代码;输入 ./mycmd -v
,就会显示版本信息。要是没有 argc
和 argv
,程序就没法知道你输入的是 -h
还是 -v
,也就没法执行不同的功能啦。
举例:编写下面的C程序
#include <stdio.h>
#include <string.h>int main(int argc, char *argv[])
{if(argc != 2){printf("Usage: %s[-a|-b|-c]\n",argv[0]);return 1;}const char* arg = argv[1];if(strcmp(arg,"-a")==0)printf("这是功能1\n");else if(strcmp(arg,"-b")==0)printf("这是功能2\n");else if(strcmp(arg,"-c")==0)printf("这是功能3\n");elseprintf("Usage: %s[-a|-b|-c]\n",argv[0]);// for(int i = 0;i<argc;i++)// {// printf("argv[%d]:%s\n",i,argv[i]);// } return 0;
}
上面的小例子,我们可以了解到 命令的选项去匹配对应功能的基本方式,
1.2 访问二进制程序
我先把概念输出
那么意思就是,系统的二进制程序(ls,pwd这样的命令)是放在了环境变量里面,bash加载完之后,如果执行环境变量的指令,bash是支持的;但是我们的程序没有放在环境变量里,就执行不了,所以,放在环境变量里面就可以执行了吗?
试试看!!!!
我把上面执行的指令,一条条写在下面,避免看不清楚:
所以,难道我不想加 ./ 的方式去访问自己的二进制程序,就把自己的二进制程序,一股脑的全部放在 /usr/bin/目录下,就ok了吗?
所以,我们还是删了放在 /usr/bin目录下的指令吧!!!
二、环境变量
2.1 打印环境变量表
打印整个环境变量表: env
呈现出来的是整个环境变量表!!!
查看环境变量的内容: echo $name //(name:你环境变量的名称)
通过名称来 标识 环境变量的唯一性
想把自己实现的 code 的二进制可执行程序 , 放到系统的环境变量里 :
如何恢复?
1) PATH=之前查到的环境变量的内容
2)其实没多大影响,因为环境变量 , 它是内存级的变量 , 在当前bash里的进程上下文里(bash开了一块空间用来存放),关掉xshell , 再查查 , 可以发现,它自己就恢复了
但是,我就是想让自己的路径添加到环境变量里面,同时,不影响系统本身的环境变量,咋办?
2.2 如何理解环境变量
你可以把环境变量理解成 bash(命令行解释器)维护的一张 “全局信息表”,里面存着系统运行、程序执行需要的各种关键信息(比如
PATH
、HOME
、SHELL
等)。以
ls
命令为例,当你在终端输入ls -a -b
时:
- bash 先拆命令:把
ls
、-a
、-b
拆成 “命令行参数”,存在命令行参数表里;- bash 查环境变量表:为了找到
ls
程序在哪,bash 会去环境变量表里找PATH
变量 ——PATH
里存着系统找命令的所有路径(比如/usr/bin
、/bin
等),bash 会按这些路径挨个找ls
程序,找到后就执行它。简单来说,环境变量是 bash 给所有程序准备的 “共享信息库”,程序运行时需要的全局配置、路径、用户信息等,都从这张 “表” 里拿。
2.3 环境变量从哪里来?
1. 系统配置文件:环境变量的 “初始模板”
Linux 系统里有一系列配置文件,比如
/etc/profile
、~/.bashrc
、~/.bash_profile
等。这些文件里写着系统默认的环境变量配置(比如PATH
的默认路径、LANG
语言设置等)。当你登录系统时,bash 会自动读取这些配置文件,把里面的环境变量加载到自己的 “环境变量表” 里。比如
~/.bashrc
是每个用户自己的配置文件,你在里面加export MYVAR=123
,下次登录时,bash 的环境变量表就会包含MYVAR
这个变量。2. 进程上下文:每个 bash 实例的 “专属副本”
如果有 10 个用户同时登录系统,就会有10 个独立的 bash 进程(每个用户一个)。每个 bash 进程都会从系统配置文件里读取环境变量,然后在自己的 “进程上下文” 里存一份副本。
这意味着:每个 bash 进程的环境变量是 “独立维护” 的。比如用户 A 在自己的 bash 里改了
PATH
,不会影响用户 B 的 bash 里的PATH
—— 就像 10 个人各拿一本 “信息手册”,自己改自己的,互不干扰。
总结一下逻辑链
系统配置文件 → bash 读取配置 → 每个 bash 进程生成自己的 “环境变量表” → 程序运行时从所属 bash 的环境变量表中获取信息。
2.4 解释windows里装软件的时候为啥要加环境变量
一、先想清楚:“不配置环境变量会怎样?”
假设你装了一个软件(比如 Python),它的可执行文件 python.exe
放在 C:\Python311\
目录下。如果不配置环境变量,每次运行 Python 时,你得在命令行里输入完整路径:
C:\Python311\python.exe 你的脚本.py
这显然很麻烦 —— 要是软件有很多可执行程序(比如编译器、工具链),每次都输全路径根本不现实。
二、配置环境变量的核心作用:“让系统自动找程序”
环境变量里的 PATH
就像 Linux 里的 PATH
一样,是系统查找可执行程序的 “路径列表”。当你把软件的安装路径加到 PATH
里后:
- 系统会按照
PATH
里的路径挨个查找你输入的命令(比如python
); - 一旦在某个路径(比如
C:\Python311\
)里找到python.exe
,就会直接执行,不需要你再输全路径。
三、结合 “用户变量 vs 系统变量” 的就近原则
Windows 里的环境变量分为用户变量和系统变量:
- 用户变量:只对当前用户生效,优先级更高(就近原则);
- 系统变量:对所有用户生效,优先级更低。
装软件时配置环境变量,本质是把软件的可执行路径 “注册” 到系统的 “查找列表” 里,让系统能自动定位到程序。比如装 Python 时,把 C:\Python311\
加到 PATH
变量里,之后你在任何目录下输 python
,系统都会在 PATH
列表里找到它并执行。
2.5 常见的环境变量
每一个环境变量都会有对应的功能,不同环境变量,功能不同!!!
HOME
HOME:指定用户的主工作目录(即用户登录到Linux系统中时,默认的目录)
SHELL
SHELL : 显示当前版本的shell
USER
USER: 当前登录的用户名(是谁登录就是谁!!!)
不同的用户登录,bash会把用户记录下来!!!
我们在env后,可以发现有一个USER,有一个LOGNAME , 它们有什么区别?
USER
:表示当前正在操作的用户,是 “动态身份”。比如你用su
切换到其他用户后,USER
会变成新用户的名字。LOGNAME
(你说的 “localname”):表示最初登录系统的用户,是 “静态身份”。哪怕你用su
切换了用户,LOGNAME
还是保持登录时的原始用户名。
HISTORY
HOSTNAME
显示当前主机名字
SHH_CLIENT
当前在哪一个用户端登录
TERM
终端类型
PWD
当前shell所在的路径
OLDPWD
记录上一次的路径
也就是cd .. : 返回上级目录 、cd - : 返回上一次执行的目录的逻辑
2.6 环境变量的相关操作
1)查看
env : 查看环境变量表
echo $xxx : 查看某一个环境变量
2)设置一个新的环境变量
3)清除环境变量
三、如何获取环境变量(代码)
3.1 命令行的第三个参数
#include <stdio.h>
int main(int argc, char *argv[], char *env[])
{(void)argc;(void)argv;for(int i = 0; env[i]; i++){printf("%s\n", env[i]);}return 0;
}
3.2 通过getenv()
#include <stdio.h>
#include <string.h>
#include <stdlib.h>int main(int argc, char *argv[])
{(void)argc;(void)argv;(void)env;char *environ = getenv("PATH");if(value==NULL)return 1;printf("PATH->%s\n",value);return 0;
}
#include <stdio.h>
#include <string.h>
#include <stdlib.h>int main(int argc, char *argv[])
{(void)argc;(void)argv;(void)env;char *who = getenv("USER");if(who==NULL)return 0;if(strcmp("who","zs")==0){printf("这是程序的正常执行逻辑\n");}else{printf("Only zs!!!\n");}return 0;
}
3.3 通过第三方变量environ获取
#include <stdio.h>
#include <unistd.h>
int main(int argc, char *argv[])
{extern char **environ;int i = 0;for(; environ[i]; i++){printf("%s\n", environ[i]);}return 0;
}
四、环境变量通常有全局性
环境变量具有全局性,能被所有子进程继承
shell 不仅仅支持环境变量,还支持本地变量
但是,如果我们env , 是查不到i , 因为i 是本地变量 , 那么 , 如何查看本地变量呢???
使用set !!!!
从上面我们可以知道,bash会记录两套变量,一个环境变量,一个本地变量;
那么本地变量有什么用呢?bash为什么支持环境变量呢?
如何把我们的本地变量,放在环境变量里面呢?
我们可以env , 看到我们的本地变量被放在了环境变量里面!为什么呢?
1)先明确几个概念
- 本地变量:只在当前 bash 会话里生效,子进程(比如你执行的
ls
、grep
这些命令)看不到它。比如你在终端里写i=100
,这个i
就是本地变量,只有当前 bash 自己知道。 - 环境变量:通过
export
命令 “升级” 后,子进程能看到它。比如export i=100
后,i
就变成环境变量,你再执行的子进程(如echo $i
)就能读到这个值。 - 内建命令(builtin command):像
export
、cd
、source
这些命令,是bash 自己内置的功能,不需要创建 “子进程” 来执行,直接由 bash 自己完成。
2)为什么export
能让本地变量变成 “子进程可见”?
export i
,是把本地变量i
“升级” 成环境变量的过程。
- 因为
export
是内建命令,它不需要创建子进程,而是由bash 自己直接修改 “环境变量表”。 - 当你执行
export i
后,i
就从 “本地变量” 变成了 “环境变量”,此时所有由当前 bash 创建的子进程(比如你后续执行的python
、ls
、echo $i
等命令)都能读到i
的值。
三、反过来:子进程能给父进程传变量吗?
不能!这是 Linux 进程模型的 “规则”:子进程无法主动修改父进程的变量。
比如你在 bash 里执行一个程序
myprog
,myprog
里给i
赋值,这个修改只能在myprog
自己的进程里生效,不会影响父进程(bash)里的i
。
总结一下逻辑
- 本地变量是 bash 的 “私有变量”,子进程看不到;
export
是内建命令,由 bash 自己执行,把本地变量 “升级” 成环境变量,让子进程能看到;- 子进程永远无法主动修改父进程的变量,这是进程的 “隔离性” 决定的。
简单来说,export
是 bash 的 “内部操作”,能让变量 “传给子进程”;但子进程没法 “反向传给父进程”~