环境变量
环境变量(environment variables)⼀般是指在操作系统中⽤来指定操作系统运⾏环境的⼀些参数;如:我们在编写C/C++代码的时候,在链接的时候,从来不知道我们的所链接的动态静态库在哪⾥,但是照样可以链接成功,⽣成可执⾏程序,原因就是有相关环境变量帮助编译器进⾏查找;环境变量通常具有某些特殊⽤途,还有在系统当中通常具有全局特性。环境变量的存储环境变量最初由操作系统和用户配置文件定义,

一.概念介绍
环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数;
如:我们在编写C/C++代码的时候,在链接的时候,从来不知道我们的所链接的动态静态库在哪里,但是照样可以链接成功,生成可执行程序,原因就是有相关环境变量帮助编译器进行查找;
环境变量通常具有某些特殊用途,还有在系统当中通常具有全局特性。
1.1 命令行参数
main 函数有参数吗?
在C语言中,main函数可以有参数,也可以没有参数。它的常见形式有两种:一种是没有参数的int main(),另一种是带参数的int main(int argc, char* argv[])。其中,argc表示命令行参数的个数,argv是一个字符串数组,存储了命令行参数的内容(char* 要么指向字符的地址,要么指向字符串的地址)。
在Linux操作系统中,main函数是由_start函数调用的。_start函数是程序的入口点,它是由操作系统加载程序时启动的。_start函数会做一些初始化工作,比如设置栈、初始化全局变量等,然后调用main函数。当main函数执行完毕后,会返回一个值给_start函数,_start函数再根据这个返回值调用exit函数来结束程序。
//code.c
#include <stdio.h>
//main函数有参数吗
int main(int argc, char* argv[])
{
for(int i = 0; i < argc; i++)
{
printf("argv[%d]:%s\n", i, argv[i]);
}
return 0;
}
编译运行结果:
lfz@hcss-ecs-ff0f:~/lesson/lesson15$ ./code
argv[0]:./code
lfz@hcss-ecs-ff0f:~/lesson/lesson15$ ./code a
argv[0]:./code
argv[1]:a
lfz@hcss-ecs-ff0f:~/lesson/lesson15$ ./code a b
argv[0]:./code
argv[1]:a
argv[2]:b
lfz@hcss-ecs-ff0f:~/lesson/lesson15$ ./code a b c
argv[0]:./code
argv[1]:a
argv[2]:b
argv[3]:c
argc表示命令行参数的数量,argv是一个字符串数组,存储了命令行输入的参数值。从运行结果可以看出,argv[0]总是程序的名称(这里是./code),以空格为分隔符,后续的argv[1]、argv[2]等依次对应输入的参数。例如,运行./code a b c时,argc为4,argv数组依次存储了"./code"、"a"、"b"和"c"。

我们知道,我们平时用的命令,他们本质上就是可执行程序,而我们用到的这些命令,他一般也是用C语言写的,而他用C语言写的,我们可以在命令后添加一个或多个选项:
我们先来看一个代码例子:
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");
}
else
{
printf("Usage: %s [-a|-b|-c]\n", argv[0]);
}
return 0;
}
从中我们可以看到main 函数的命令行参数的用途是可以让一个程序可以通过选项,可以实现不同的子功能。这也是我们之前使用指令的时候为什么会有选项,因为main函数可以带命令行参数的!!!(指令选项的实现原理)
命令行中输入的字符串最终会被Shell(如Bash)进行切分处理,然后将切分后的字符串作为参数传递给程序,构建argv[]数组。
Shell 的字符串切分机制(切分方法之一)
当你在命令行中输入一个命令时,Shell会根据默认的分隔符(如空格、制表符等)将输入的字符串切分成多个部分。这些部分被存储在argv[]数组中,其中argv[0]通常是程序的名称,argv[1]、argv[2]等则是用户输入的参数。
例如,输入命令./example -a param1时,Shell会将其切分为./example、-a和param1,分别存储到argv[0]、argv[1]和argv[2]中。
所以在我们的进程启动时,我们的进程拥有一张表,叫做argv表,用来支持实现选项功能。
二. 一个例子,一个环境变量
我们发现我们执行自己的命令的时候(比如:./code)是要带./的,而在执行系统命令的时候是不带./的:

无论是否在系统当中,我们写的二进制程序和系统的指令是没有本质区别的,我们使用的指令本来就是系统预装的二进制程序,那为什么一个带路径,一个不用带呢?
虽然现在我们还解释不清楚,但是我们有一点应该知道:
要执行一个程序,必须先找到它!!!
所以我们运行我们自己的程序时,我们需要./,就是表明我们要执行的程序在当前路径下,可是为什么执行我们的系统命令就不需要呢?原因是系统当中存在环境变量,来帮助找到目标二进制文件!!!
lfz@hcss-ecs-ff0f:~/lesson/lesson15$ ls /usr/bin/ls
/usr/bin/ls
这是ls命令所在的路径。

要执行命令,首先需要找到它,所以我们运行我们的程序时,我们直接 code -a 它就找不到,所以执行一个命令的时候,默认不会在我们当前路径下找的,他不会找的!
换句话说:如果我们把code拷贝到/usr/bin目录下,是不是我们在执行code的时候就不需要再带路径了呢?答案是:是的。我们看看下面的试验:
我们(非root)直接拷贝到指定目录下是不行的:

他的拥有者 / 所属组是root,对于other不给写的权限:

我们应该使用:
sudo cp code /usr/bin/
我们就可以得到: 

我们就可以直接不带路径就可以使我们的二进制程序运行了。
但是我们强烈不建议将我们写的二进制文件拷贝到系统当中,因为我们写的二进制文件没有经过严格的测试,他人的发布流程,代码也没有经过时间的验证,代码可能会有bug,可能污染原本系统的指令池:我们测试完就将其删除吧😊
sudo rm /usr/bin/code
我们完整的过程验证了,但是这里就出现了新的问题了,我们的最终结论就是:系统是默认认识/usr/bin/路径目录下的,所以我们将我们写的二进制程序cp到该目录下,系统就认识了,就不需要指定路径了,可是系统凭什么它能够认识/usr/bin/路径下,他怎么就知道我们执行命令的时候就必须得去我们对应的/usr/bin/路径下去查呢?
因为系统当中会存在环境变量,这个环境变量就是:PATH
所以在Linux系统当中,存在一个环境变量PATH,这个环境变量PATH,他默认情况下,是在我们系统当中存在的,用来标识一串路径,PATH里面表明的是:告诉系统去哪些路径下找二进制文件,就是在系统中搜索指令的默认搜索路径!!!
2.1 查看环境变量
在Linux系统当中,我们想要查看所有的环境变量,我们可以使用:
env
#environment的简写

将系统当中所有的环境变量给罗列出来。
环境变量它是一个变量,所以环境变量的构成是:
名称 = 内容
我们标识环境变量的唯一性,我们都是通过名称来标识的。
我们想要看一个环境变量的内容,我们可以使用:
echo $环境变量名称
#要加$,不让就变成了直接打印字符串了

我们系统在搜索某些命令时,就默认在这些路径下搜索,并且这些路径都是以绝对路径的方式呈现的,我们发现路径与路径之间会有 ":",说人话就是,我们要执行一个ls命令的时候,操作系统它不是默认在/usr/bin/路径下,默认是去查这个PATH环境变量,而PATH环境变量找的时候,它会以:作为分隔符,先在第一个路径找,没有找到ls就接着往后面找,以此类推,如果把所有路径都遍历一次,发现都没有找到,就会给我们报:command not found

找到了ls,我们就加载ls并执行它。
正是因为/usr/bin/在环境变量里,所以ls命令,才能够正常的被系统层(Shell)找到,可以不带路径执行ls。
那么归根结底在系统内找到我们对应的某一个命令,某一个二进制文件,默认是在环境变量PATH所对应的这些子路径当中一个一个的去找,所以我们上面将我们的code拷贝到/usr/bin路径下,这个code就能够直接被系统直接找到了,那要是把我们自己当前的路径添加到PATH这个环境变量里,那么此时我们自己不就可以不使用上面的方式到达直接使用code命令,不用直接显示路径来执行code命令了,这肯定是对的,那么我们应该如何添加呢?
因为PATH本来就是一个环境变量,我们直接可以使用:
PATH=我们要填入的绝对路径(填当前,就是当前pwd的结果)

但是我们发现PATH的内容直接被覆盖了,这也是我们对变量赋值的行为:(ls等指令直接使用不了了)所以我们不应该直接赋值进而导致覆盖,我们可以将其:后接上我们要填入的绝对路径!!!

当然,我们重新启动我们的Shell,就可以回到最初状态,恢复原本的环境变量内容。
2.2 如何理解环境变量呢?存储的角度
我们环境变量的值最终都是被谁保存起来的呢?包括我们执行命令时,是谁来在系统里找我这个命令呢?
答案是:bash!,也就是我们一旦登入的时候,系统就会给我们创建一个bash进程,而bash就必须从系统当中读取我们所有的环境变量的信息,然后在bash进程内部形成一张表,称为环境变量表,就是一个指针数组:

表的结尾为NULL,可用于遍历。
我们所查看到的所有环境变量,其实是一个一个的字符串,0指针指向第一个,1指针指向第二个,依次类推,所以在bash进程启动的时候,在他自己内部会构建一张表,当我们在输指令时,假设输入ls -a -b时,我们已经知道了,首先这个命令行,这个字符串并不是被子进程拿到的,是先被bash先拿到,这时候就会构建第一张表:命令行参数表,接着bash拿着命令的名字,找到第二张表:环境变量表,再结合环境变量PATH,再根据PATH一个个的路径,把每个路径拼上我们的程序名,然后由bash在系统当中找对应的命令是否存在,如果在,就创建子进程再运行他。
【创建子进程的前奏是创建表,读取表,利用表】
总结就是:bash内部有两张表,一个叫作命令行参数表,一个叫作环境变量表。
所以环境变量就是K_Value的长字符串,那么在bash启动的时候,他会想办法在我们自己的bash内部(bash是C/C++写的程序),他new/malloc出一段空间,然后给每个环境变量再new/malloc出一段空间,形成一个二维数组,然后把环境变量字符串依次拷贝到这个表里面,所以bash就在内部维护了这张表,所以我们env查的时候,就是打印这张表的内容。
2.3 环境变量最开始从哪里来的呢?
是由系统的相关配置文件中来的!!!
所谓的环境变量的配置信息是在系统上,但是系统在每一个用户的家目录中存了两个隐藏文件:(CentOS)

用户可以在自己的主目录中定义个人的环境变量,如 ~/.bashrc、~/.bash_profile 等。这些文件在用户登录时自动加载。
我使用的是 Ubuntu,所以是在 .profile 文件当中:

我们就可以在此当中添加我们的路径信息:
在Ubuntu中,.profile文件是用户登录时由Shell(如bash)读取的配置文件之一。你可以在.profile文件中添加自定义的路径信息,以便在登录时自动设置环境变量(如PATH)。
以下是如何在.profile文件中添加路径信息的步骤:
1. 打开
.profile文件在终端中输入以下命令,打开
.profile文件:vim ~/.profile2. 添加路径信息
在
vim中,你可以通过以下步骤添加路径信息:步骤1:进入插入模式
按下
i键进入插入模式,光标会变成插入状态(通常会显示-- INSERT --)。步骤2:定位到合适的位置
使用方向键将光标移动到你想要添加路径信息的位置。通常建议将路径信息添加到文件的末尾,或者在
PATH相关的部分。步骤3:添加路径信息
在插入模式下,输入以下内容(假设你要添加
~/bin和~/.local/bin到PATH):# Add custom directories to PATH export PATH="$HOME/bin:$HOME/.local/bin:$PATH"3. 保存并退出
完成编辑后,按照以下步骤保存并退出
vim:步骤1:退出插入模式
按下
Esc键退出插入模式。步骤2:保存并退出
输入以下命令并按下
Enter键::wq
:w表示保存文件。
:q表示退出vim。
:wq表示保存并退出。4. 使更改生效
为了让更改立即生效,可以运行以下命令:
source ~/.profile或者,你也可以重新登录系统,使更改生效。
5. 验证更改
运行以下命令,检查
PATH变量是否包含你添加的路径:echo $PATH你应该会看到类似以下输出(包含你添加的路径):
/home/your_username/bin:/home/your_username/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games6. 示例
假设你有一个自定义的脚本目录
~/scripts,你可以将其添加到PATH中:# Add ~/scripts to PATH export PATH="$HOME/scripts:$PATH"保存并使更改生效后,你就可以直接在命令行中运行
scripts目录中的脚本了。7. 注意事项
避免重复添加路径:在添加路径之前,确保该路径尚未存在于
PATH中,以避免重复。使用
export命令:确保使用export命令,使变量在子进程中可用。备份
.profile文件:在修改.profile文件之前,建议备份原始文件:cp ~/.profile ~/.profile.bak通过在
.profile文件中添加路径信息,你可以方便地管理和使用自定义的命令和脚本。
操作我们对于本篇不算很重要,重要的是我们要知道环境变量是从我们系统的配置文件中来的。
概括:
内核复制父进程(bash)的:地址空间环境变量表
envp文件描述符表子进程触发
execve 系统调用内核做三件核心事:销毁子进程原有地址空间加载 ls 可执行文件到进程空间把 父进程传下来的 argv(命令行参数)、envp(环境变量表) 固化到新进程内核栈 / 进程描述符中内核调度子进程运行,执行 ls 逻辑。
每个进程在内核里有:task_struct保存:arg_start/arg_end 命令行参数区间保存:env_start/env_end 环境变量区间
进程用户态空间里一块字符串区域,key=value 格式,内核维护起止地址,execve 会原样传给新程序。
execve 系统调用内核原型
// 内核态系统调用入口
long sys_execve(
const char __user *filename,
const char __user *const __user *argv,
const char __user *const __user *envp
);
argv:命令行参数表(ls -l)envp:环境变量表(从 bash 继承而来)
模拟内核行为的极简内核风格代码(伪内核 C 代码)
不是用户态 glibc,是内核视角逻辑,模拟内核怎么存进程、传 argv/envp、fork 继承、exec 替换。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <mem.h>
// 模拟:内核环境变量最大长度
#define ENV_BUF_LEN 1024
#define ARG_MAX 8
// 模拟:内核维护的进程环境变量 + 命令行参数结构
struct task_struct {
// 命令行参数表
char* argv[ARG_MAX];
// 环境变量表:字符串数组 key=value
char* envp[ARG_MAX];
// 标记是否是子进程
int is_child;
};
// 模拟内核:从 .profile 加载环境变量(内核只提供文件读取能力,解析在用户态bash)
void kernel_load_profile(struct task_struct* p)
{
// 模拟 bash 读取 .profile 后设置到进程环境变量
p->envp[0] = "PATH=/bin:/usr/bin:/usr/local/bin";
p->envp[1] = "HOME=/home/ubuntu";
p->envp[2] = NULL; // 结尾哨兵
printf("[内核] 为进程初始化环境变量表\n");
}
// 模拟内核 fork:复制父进程 argv、envp 给子进程
struct task_struct* kernel_fork(struct task_struct* parent)
{
struct task_struct* child = (struct task_struct*)malloc(sizeof(struct task_struct));
// 拷贝命令行参数
memcpy(child->argv, parent->argv, sizeof(parent->argv));
// 拷贝环境变量表(内核写时复制机制逻辑)
memcpy(child->envp, parent->envp, sizeof(parent->envp));
child->is_child = 1;
printf("[内核] fork 创建子进程,继承父进程 argv、envp\n");
return child;
}
// 模拟内核 execve:加载新程序,使用传入的 argv 和 envp
void kernel_execve(struct task_struct* p, const char* bin_path)
{
printf("[内核] 执行 execve 加载程序: %s\n", bin_path);
printf("[内核] 本次执行的命令行参数:\n");
for (int i = 0; p->argv[i]; ++i)
{
printf(" argv[%d]: %s\n", i, p->argv[i]);
}
printf("[内核] 继承的环境变量 PATH:\n");
for (int i = 0; p->envp[i]; ++i)
{
if (strstr(p->envp[i], "PATH="))
{
printf(" %s\n", p->envp[i]);
break;
}
}
printf("[内核] 调度子进程运行 ls 程序\n");
}
int main()
{
// 1. 内核创建 bash 进程
struct task_struct bash_proc;
memset(&bash_proc, 0, sizeof(bash_proc));
// 2. 内核协助 bash 加载 .profile 环境变量
kernel_load_profile(&bash_proc);
// 3. bash 解析用户输入 ls -l,填充自己的 argv
bash_proc.argv[0] = "ls";
bash_proc.argv[1] = "-l";
bash_proc.argv[2] = NULL;
// 4. 内核 fork 出子进程
struct task_struct* child = kernel_fork(&bash_proc);
// 5. 内核 execve 子进程,传入参数表、环境变量表
kernel_execve(child, "/usr/bin/ls");
free(child);
return 0;
}
最后,如果Linux系统有 10 个用户登入呢?
我们运行的命令的父进程都是bash,那么有 10 个用户登入,就要有 10 个bash,就需要从对应的配置文件当中读到自己的bash上下文里。
指令的查找工作是由bash自己完成的,“执行一个命令就需要找到他”,就是bash在找,通过PATH来找,也就是通过环境变量来找!!!
在Windows中,我们可以通过系统属性中的 “环境变量” 选项配置环境变量,包括用户变量和系统变量。具体步骤是:右键点击 “此电脑” 选择 “属性”,进入 “高级系统设置”,点击 “环境变量”,在弹出的窗口中可以新建或编辑用户变量和系统变量。
三. 认识更多的环境变量
常用环境变量及其作用
| 环境变量名称 | 作用 |
|---|---|
PATH |
指定可执行文件的搜索路径 |
HOME |
存储用户的主目录路径 |
SHELL |
指示用户使用的 shell 类型 |
LANG 和 LC_* |
设置语言环境和区域设置 |
TERM |
指示终端类型 |
EDITOR 和 VISUAL |
指定默认的文本编辑器 |
PS1 |
定义命令行提示符 |
MAIL |
指示邮件存放的位置 |
LOGNAME |
显示当前登录用户的用户名 |
HISTFILE |
指定命令历史记录文件的位置 |
TZ |
设置时区 |
LD_LIBRARY_PATH 或 DYLD_LIBRARY_PATH |
指定动态链接库的搜索路径 |
四. 获取环境变量的方法
在 Linux 系统中,可以通过多种方法获取环境变量。以下是详细的获取方法:
1. 使用命令行工具
1.1 env 命令
-
功能:显示当前所有的环境变量及其值。
-
语法:
env -
示例:
env | less这将显示所有环境变量,并通过
less命令分页查看。
1.2 printenv 命令
-
功能:显示当前所有的环境变量及其值,类似于
env命令。 -
语法:
printenv -
示例:
printenv | grep PATH这将显示包含
PATH的环境变量。
1.3 echo 命令
-
功能:显示指定环境变量的值。
-
语法:
echo $变量名 -
示例:
echo $PATH这将显示
PATH环境变量的值。
1.4 set 命令
-
功能:显示当前 shell 中的所有变量,包括环境变量和局部变量。
-
语法:
set -
示例:
set | grep PATH这将显示包含
PATH的变量。
2. 在脚本中获取环境变量
2.1 使用 $ 符号
-
功能:在脚本中获取环境变量的值。
-
语法:
$变量名 -
示例:
#!/bin/bash echo "PATH: $PATH" echo "HOME: $HOME"
2.2 使用 ${} 语法
-
功能:在脚本中获取环境变量的值,支持变量名为空或包含特殊字符的情况。
-
语法:
${变量名} -
示例:
#!/bin/bash echo "PATH: ${PATH}" echo "HOME: ${HOME}"
3. 在编程语言中获取环境变量
3.1 C 语言
-
功能:使用
getenv函数获取环境变量的值。(通过系统调用)putenv , 后⾯讲解 -
语法:
#include <stdlib.h> char *value = getenv("变量名"); -
示例:
#include <stdio.h> #include <stdlib.h> int main() { char *path = getenv("PATH"); if(psth == NULL) return 1; printf("PATH: %s\n", path); return 0; }
如果我想写一个程序,只有我这个用户能执行,其他人,包括root一律不让执行,我们应该如何设计?
对于环境变量的认识,我们知道,现在只有一个人知道登录用户是谁,这个人就是bash,也就是说在整个系统当中,只有bash知道登录用户是谁,我们可以如下设计:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char* argv[], char* envp[])
{
(void)argc;
(void)argv;
(void)envp;
const char* who = getenv("USER");
if(who == NULL)
{
return 1;
}
if(strcmp(who,"lfz") == 0)
{
printf("这是程序的正常执行逻辑!\n");
}
else
{
printf("Only lfz!!!\n");
}
return 0;
}

- 功能: 使用environ,这是一个二级指针,因为我们环境变量表就是一个二维数组
- 语法:要声明:

- 示例:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
//声明
extern char**environ;
int main(int argc, char* argv[])
{
(void)argc;
(void)argv;
for(int i=0;environ[i];i++)
{
printf("environ[%d]->%s\n", i, environ[i]);
}
return 0;
}
3.2 Bash 脚本
-
功能:在 Bash 脚本中获取环境变量的值。
-
语法:
$变量名 -
示例:
#!/bin/bash echo "PATH: $PATH" echo "HOME: $HOME"
4. 获取环境变量的值并赋值给变量,以及增删变量
4.1 使用 read 命令
-
功能:将环境变量的值赋值给变量。
-
语法:
read 变量名 <<< $(env | grep 变量名 | cut -d'=' -f2) -
示例:
read path <<< $(env | grep PATH | cut -d'=' -f2) echo "PATH: $path"
4.2 使用 export 命令
-
功能:将变量赋值给环境变量。也可以在环境变量中新增一组K_Value(环境变量)(有则改,无则增)
-
语法:
export 变量名=值 -
示例:
export PATH=$PATH:/new/path export MYENV=11223344
4.3 使用 unset 命令
- 功能:删除环境变量
- 语法:
unset 变量名
- 示例:
unset MYENV
5. 获取环境变量的值并进行操作
5.1 使用 cut 命令
-
功能:从环境变量中提取特定部分。
-
语法:
env | grep 变量名 | cut -d'=' -f2 -
示例:
env | grep PATH | cut -d'=' -f2
5.2 使用 awk 命令
-
功能:从环境变量中提取特定部分。
-
语法:
env | awk -F'=' '/变量名/ {print $2}' -
示例:
env | awk -F'=' '/PATH/ {print $2}'
五. main函数的问题
main函数最多有几个参数?
在 C 语言中,main 函数最多可以接收两个参数。这两个参数分别是:
-
argc:表示命令行参数的数量,包括程序名称本身。 -
argv:是一个指向字符指针数组的指针,存储了所有命令行参数的字符串。
例如,main 函数的定义通常如下:
int main(int argc, char *argv[]) {
// 程序代码
return 0;
}
此外,还有一些扩展形式的 main 函数,例如在某些系统中,main 函数可以接收第三个参数 envp,用于访问环境变量。这种形式的 main 函数定义如下:
int main(int argc, char *argv[], char *envp[]) {
// 程序代码
return 0;
}
但需要注意的是,这种带有 envp 参数的形式并不是 C 标准 的一部分,而是某些编译器或操作系统提供的扩展。在标准 C 中,main 函数的参数最多为两个。
总的来说:main 函数最多有 3 个参数。
我们写的代码变成进程的时候是子进程,是由 bash 父进程创建的,所以 main 对应的命令行参数和命令行参数表、环境变量表其实是父进程 bash 传递给 main 的,怎么传递的,在我们后面的程序替换 / 程序加载会聊到。
#include <stdio.h>
#include <string.h>
int main(int argc, char* argv[], char* envp[])
{
(void)argc;
(void)argv;
for(int i=0;envp[i];i++)
{
printf("envp[%d]->%s\n", i, envp[i]);
}
return 0;
}
-
(void)argc;和(void)argv;:这两个语句用于告诉编译器argc和argv是故意未使用的,避免编译器发出警告。
我们自己导入的环境变量是可以被上面的子进程拿到的,也就是环境变量可以被子进程继承:

环境变量也是可以被往下继承的(孙子进程....),所以所环境变量通常具有全局特性。
六. 理解环境变量的特性
6.1 环境变量具有全局特性
我们上面的进程间环境变量的继承关系就说到了:环境变量具有全局特性
示例代码:Linux 上的 C 语言程序展示进程间环境变量的继承
以下是一个简单的 C 语言程序,展示如何在父进程中设置环境变量,并在子进程中继承和访问这些环境变量:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
// 父进程定义一个环境变量
const char* my_var = "Hello World";
setenv("MY_VAR", my_var, 1);
// 创建子进程
pid_t pid = fork();
if (pid == -1) {
// 创建子进程失败
perror("fork");
return 1;
} else if (pid == 0) {
// 子进程
const char* child_var = getenv("MY_VAR");
printf("子进程中的 MY_VAR 值为: %s\n", child_var);
} else {
// 父进程
printf("父进程中的 MY_VAR 值为: %s\n", my_var);
}
return 0;
}
-
设置环境变量:使用
setenv函数在父进程中设置环境变量MY_VAR,其值为"Hello World"。 -
创建子进程:使用
fork函数创建一个子进程。 -
子进程继承环境变量:子进程通过
getenv函数获取环境变量MY_VAR的值,并打印出来。 -
父进程输出:父进程直接打印其设置的环境变量
my_var的值。
运行程序后输出如下:
父进程中的 MY_VAR 值为: Hello World
子进程中的 MY_VAR 值为: Hello World
通过上述代码,我们可以看到子进程成功继承了父进程的环境变量,并且可以在子进程中访问和使用这些环境变量。
6.2 补充两个概念:为后面埋伏笔
a. Shell 变量赋值规则
Shell也支持一个变量名等于一个变量值,中间左右两则不能带空格,否则会当成命令而报错:

所以:
在 Shell 中,变量赋值的语法是 变量名=变量值,等号两侧不能有空格。如果变量值包含空格或其他特殊字符,需要用引号包围。例如:
variable_name=value
variable_name='value with spaces'
variable_name="value with spaces"
这种定义出来的变量称为本地变量
我们使用env是看不到我们定义的如上的 i 变量的!!!不是环境变量。
我们可以使用:
set
显示环境变量和本地变量,也就是说bash会记录两套变量!
所以bash内部其实有3张表,但是我们重点的是上面的两张表!!!
我们也可以看出:本地变量不会被子进程继承,只能在bash内部被使用!!!
那bash为什么要有本地变量,他又有什么用呢?
其实本地变量我们早就使用过了:
i=0; while [ $i -le 10 ]; do echo $i; let i++; done

就是让 bash 可以成为一门语言嘛,这样的话就可以定义变量了嘛,就可以用了。(就是要有本地变量的语法支持)
本地变量的作用域限制在当前函数内,不会影响外部环境中的同名变量。
b. 导入环境变量(export XXX)是导到谁里面了?
我们的环境变量是在 bash 的上下文中的,export这样的命令,它是命令啊!!!在我们 bash 的子进程去执行export命令的话,export就是一个子进程,子进程导环境变量是把数据放在了父进程 bash环境变量表里面,不是说进程之间是有独立性的吗?也就是说父进程 bash 可以将环境变量交给子进程,但是子进程是没有办法将数据交给父进程的,因为没有反向继承关系啊!所以export这样的命令一旦被执行,它怎么可能将环境变量导给 bash 呢?
只有一种解释:export 这个命令和其他的命令不一样:
子进程不能修改父进程的环境变量,因为每个进程都有自己的独立内存空间和环境变量表。然而,export 命令在 Bash 中的工作方式有些特殊,它实际上并不是通过子进程来修改父进程的环境变量,而是直接在当前 Shell 环境中操作。
让我们详细解释一下这个过程:
export 命令的工作原理:
export 我们称为内建命令(built-in command),是一个 Bash 内置命令,而不是一个外部可执行文件。这意味着当你在 Bash 中执行 export 命令时,它不会创建一个新的子进程,而是在当前的 Bash 环境中直接操作,让 bash 自己亲自执行,一般都是 bash 自己调用函数,或者系统调用完成的。因此,export 命令可以直接修改当前 Shell 环境的环境变量表。
在后面谈到进程控制后,我们会进行对应的操作😎【其实很简单的,就是直接在父进程当中直接操作而已,之前的大部分是创建子进程来进行”帮忙的“】
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)