一.概念介绍

环境变量(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时,argc4argv数组依次存储了"./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-aparam1,分别存储到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内部(bashC/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 ~/.profile

2. 添加路径信息

vim中,你可以通过以下步骤添加路径信息:

步骤1:进入插入模式

  • 按下i键进入插入模式,光标会变成插入状态(通常会显示-- INSERT --)。

步骤2:定位到合适的位置

  • 使用方向键将光标移动到你想要添加路径信息的位置。通常建议将路径信息添加到文件的末尾,或者在PATH相关的部分。

步骤3:添加路径信息

  • 在插入模式下,输入以下内容(假设你要添加~/bin~/.local/binPATH):

    # 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/games

6. 示例

假设你有一个自定义的脚本目录~/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 -lenvp:环境变量表(从 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 函数最多可以接收两个参数。这两个参数分别是:

  1. argc:表示命令行参数的数量,包括程序名称本身。

  2. 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;:这两个语句用于告诉编译器argcargv是故意未使用的,避免编译器发出警告。

我们自己导入的环境变量是可以被上面的子进程拿到的,也就是环境变量可以被子进程继承:

环境变量也是可以被往下继承的(孙子进程....),所以所环境变量通常具有全局特性。

六. 理解环境变量的特性

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;
}
  1. 设置环境变量:使用 setenv 函数在父进程中设置环境变量 MY_VAR,其值为 "Hello World"

  2. 创建子进程:使用 fork 函数创建一个子进程。

  3. 子进程继承环境变量:子进程通过 getenv 函数获取环境变量 MY_VAR 的值,并打印出来。

  4. 父进程输出:父进程直接打印其设置的环境变量 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 环境的环境变量表

在后面谈到进程控制后,我们会进行对应的操作😎【其实很简单的,就是直接在父进程当中直接操作而已,之前的大部分是创建子进程来进行”帮忙的“】

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐