【寻找Linux的奥秘】第六章:环境变量

发布于:2025-05-20 ⋅ 阅读:(17) ⋅ 点赞:(0)

QQ20250518-183929

前言

本专题将基于Linux操作系统来带领大家学习操作系统方面的知识以及学习使用Linux操作系统。上一章我们简单认识了进程,本章将讲解操作系统中一个重要的概念——环境变量。

1. 基本概念

**环境变量(Environment Variables)**是一种动态命名值,用于影响操作系统及其运行程序的行为。环境变量可以在系统层面存储信息,使得不同的程序和进程能够共享和访问这些信息。

我们在安装一些软件的时候需要我们配置相应的环境变量,例如我们在安装python时,可能并不是只安装python,它可能还安装了一些其他的工具,所以我们需要通过环境变量把所有的工具全部找到;又例如我们在编写C/C++代码的时候,在链接的时候,我们从来不知道所链接的动态静态库在哪⾥,但是照样可以链接成功,⽣成可执⾏程序,原因就是有相关环境变量帮助编译器进⾏查找。

环境变量分为很多种,每一种都有其特殊的用途,在系统中通常具有全局性。

2. 命令行参数

在认识环境变量之前,我们先来补充一个知识点。

我们知道,在C/C++程序中,main函数是我们程序的入口。既然是函数,那么它就要被调用。那main函数有没有参数呢?其实main函数也是有参数的。对于系统来说第一个执行的函数并不是main函数,在Linux中有一个_start函数,它作为第一个执行的函数去调用main函数,main函数既然被调用,那么就会传参。下面让我们来看一看main函数有哪些参数:

#include <stdio.h>
 
 int main(int argc, char *argv[], char *env[])
 {
     return 0;
}

第二个参数argv是一个指针数组,它指向的是一个个的字符串,第一个参数argc是一个整数,它代表的是agrv中元素的个数。对于argv我们并不知道它里面存放的都是什么,那么我们可以编写代码看一看:

 #include <stdio.h>
 
 int main(int argc, char *argv[], char *env[])
 {
     for(int i = 0; i < argc; i++)
     {
         printf("argv[%d]: %s\n", i, argv[i]);
     }
     return 0;
}

下面来让我们看一下运行结果:

QQ20250518-200958

可以看到,当我们./运行时,它显示的是我们程序的名字,那么如果我们在后边再加上一些字符呢?让我们看看效果:
QQ20250518-201156

可以发现,argv其实就是把命令行参数以空格为分隔符切割成一个个的字符串变成我们的指针数组。

QQ20250518-203812

我们的命令行参数也就是一个长字符串,当我们执行C/C++程序时,这个字符串就会被以空格为分隔符切分成若干份,然后将每一份的地址填入到argv中,数组中有效元素的个数就是argcargv中以NULL结尾。

在Linux中,我们平时用的命令本质上都是可执行程序,这些命名基本上也都是用C语言写的。我们在使用命令时也会有许多指令,这与我们上面讲的是不是相对应了呢?是的,所以main函数的命令行参数是实现不同子功能的方法!这也就是我们使用命令时可以带指令选项的实现原理。

命令行参数是由bash来切分的。

之前我们写的程序之所以main函数不带参数是因为我们的程序功能就一个,执行完就可以了。

所以在我们的进程启动时,它拥有一张表——argv表,用来支持实现选项功能。

3. 认识环境变量

我们在执行我们的程序时需要在程序名前加上./,而执行系统的命令却可以直接使用程序名,这是因为什么呢?

QQ20250518-210312

我们写的二进制程序和系统中自带的二进程有本质的区别吗?实际上它们并没有区别。我们运行一个程序时,首先要做的就是先找到它,所以我们在运行我们自己写的程序时需要带上路径,那么为什么执行系统程序时就不需要呢?原因就是系统中存在环境变量,来帮助系统找到目标二进制文件!

所以在我们执行我们自己的程序时哪怕在当前路径下也需要带上路径,这意味着执行一个命令时默认不会再当前路径下找

QQ20250518-210839

而之前我们讲过,我们所使用的各种系统命令都是存放在/usr/bin/目录下的,那么我们如果把我们自己的程序放到该路径下是不是就可以不带路径就可以执行了呢?是的,下面让我们验证一下:
QQ20250518-211330

可以看到,当我们把程序放在/usr/bin/目录下,不用路径程序也能执行。

当然并不推荐大家这样做,我们写的程序放到系统默认的路径下就会污染系统的指令池。

那么系统为什么知道/usr/bin/路径呢?它是怎么知道我们运行命令的时候就必须要从/usr/bin/路径下查呢?这是因为系统中存在一个环境变量PATH,这个环境变量所代表的是系统中搜索指令的默认搜索路径。

在Linux中我们可以使用env指令去查看所有的环境变量:
QQ20250518-212248

环境变量是一个变量,所以它的构成是名称和内容,所以标识一个环境变量的唯一性都是通过名称来标识的。

在Linux中我们只想查看一个环境变量的内容,可以通过

echo $(环境变量名)

来查看:

QQ20250518-212704

在命令行中我们可以通过$符号去查看变量的值。

观察PATH的值,我们可以发现都是一些绝对路径,并且路径与路径之间以:隔开。所以当我们要执行一个命令时,操作系统会去查PATH这个环境变量,在PATH中的每一个路径中去查找,如果在PATH中的所有路径中都没有找到目标命令,那么系统就会报错,也就是command not fount

所以就是因为/usr/bin/路径在PATH这个环境变量里,所以我们在执行ls这些命令时才会被系统找到,所以执行ls不用带路径。归根结底在系统中找到某一个命令或二进制文件默认是在环境变量PATH中找的。我们所以我们把程序拷贝到/usr/bin/路径里就可以被系统直接找到了。

那么如果我们把我们程序所在的路径添加到PATH环境变量中是不是也可以做到相同的事情呢?答案是肯定的,那么我们如何修改PATH的值呢?既然是一个变量,我们就可以直接给它赋值:

QQ20250518-214406

可以看到,我们是可以给PATH赋值的,而且code也可以正常执行,但是ls指令却无法执行了,这是因为我们是直接给PATH赋值,相当于覆盖了PATH之前的值,不过不用慌,因为PATH是内存级的变量,它储存在当前bash的进程上下文中的,所以我们退出重新登录就会发现PATH就重回原样了:

QQ20250518-214919

至于为什么PATH没有后echo命令还可以执行,是因为echo命名属于内建命令,后面再讲。

那么如何正确的添加环境变量呢?我们可以通过指令PATH=$PATH+路径来进行:
QQ20250518-215215

所以我们可以不带路径就能执行我们自己的程序了。当然,这种修改也是临时的,当我们退出登录后就会失效。

4. 理解环境变量

环境变量对应的值是被谁保存起来的呢?我们执行命令时又是谁在系统中去寻找的呢?答案是bash!也就是说当我们登录的时候,系统就会给我们创建一个bash进程,而bash会从系统中读取环境变量的信息,然后在bash进程内部形成一张表——环境变量表。和argv一样,它也是指针数组,每一个环境变量就是一个字符串:

QQ20250518-220736

所以当我们执行一个程序时,bash会先拿到命令行参数,然后拆分,构建命令行参数表。然后bash会根据程序的名字,在环境变量表中找到PATH,再根据PATH中的路径去找对应的命令是否存在。所以在bash内部默认有两张表,一个叫命令行参数表,一个叫环境变量表。

所以环境变量究竟是什么呢?实际上环境变量就是Key-Value格式的长字符串!所以当我们使用env命令查环境变量实际上就是打印环境变量表。

环境变量最开始是从系统的相关配置文件中来的,所以在bash启动时它会在配置文件中读取所有的环境变量值,然后在自己内部创建自己的环境变量表。

也就是说如果我们想要永久更改对应的环境变量,就需要到对应的配置文件中去修改。我们可以看一下环境变量的调用关系:在每一个用户的家目录下有两个名为.bashrc.bash_profile的隐藏文件。

.bash_profile文件会要求加载.bashrc文件,而.bashrc中要做的就是加载系统中的环境变量的值,这也就是它们的调用关系

QQ20250518-222725

所以在bash进程创建时就会读取.bashrc.bash_profile两个配置文件的内容,构建出环境变量。

如果Linux中有十个用户登录,那么就会有十个bash进程,每一个bash都会从配置文件中读取构建自己的环境变量表。

所以在开始我们说一个程序要被执行,首先要找到这个程序,那么是谁找呢?是由bash去找,bash通过PATH去找,也就是环境变量。

5. 常见的环境变量

  • PATH : 指定命令的搜索路径。也就是我们上面讲的。QQ20250518-212704

  • HOME: 指定⽤⼾的主⼯作⽬录(即⽤⼾登陆到Linux系统中时,默认的⽬录)QQ20250518-224529

    我们可以通过cd ~命令直接进入到我们的家目录,那么这是为什么呢?其实系统在执行cd命令时,检测到~号就会直接把它替换成环境变量HOME的值。因为我们每个用户登录的时候bash就会根据当前的用户形成一个家目录,写进环境变量HOME中。

  • SHELL:用于指示用户当前使用的命令行解释器(shell)的路径。它的值通常是/bin/bash。 QQ20250518-225537

  • USER:用于表示当前登录用户。USER在某些情况下与登录名不一定相同,尤其是当使用 su 命令切换用户时。QQ20250518-225730

  • LOGNAME:用于存储用户登录系统时使用的用户名。LOGNAMEUSER 环境变量通常会存储相同的值,LOGNAME 更强调用户的登录名。 USER 更强调当前被使用的用户名。QQ20250518-230301

  • HISTSIZE:定义了在使用shell 时,命令历史记录的最大条目数。它指示 shell 存储历史命令的数量。QQ20250518-230856

  • HOSTNAME:用于存储计算机的主机名(或计算机名称)。它在网络和系统管理中非常重要。QQ20250518-231011

  • PWD:用于表示当前工作目录的完整路径。PWD 变量会随着你在文件系统中的位置变化而自动更新。例如,如果你使用 cd 命令更改目录,PWD 的值也会相应更新。QQ20250518-231745

上面这些是我们常见的一些环境变量,可以看到,这些环境变量的功能都不太一样,代表每一个环境变量都有它特定的作用。

6. 相关操作

  • echo $NAME:查看具体的环境变量的值。
  • env:查看所有的环境变量。
  • export [key-value]:导入新的环境变量。我们可以通过export命令在环境变量表中导入新的环境变量:QQ20250518-232434
  • unset [name]:取消某一个环境变量。QQ20250518-232614

在代码中获取环境变量

1. main函数的参数

这些都是一些命令,那么我们如何在代码中获得环境变量呢?前面我们讲命令行参数时,我们说main函数是否有参数时除了argcargv外,还有一个env,这个env其实就是我们的环境变量表。并且在main函数中我们如果要写参数,最多也只有这三个。这些参数是由父进程传递给我们的。

 #include <stdio.h>
 
 //main函数有参数
 //最多有三个
 //是父进程传递给我们的
 int main(int argc, char *argv[], char *env[])
 {   
     (void)argc;
     (void)argv;
     //虽然我们不知道env的元素个数,但是我们知道env是以NULL结尾的
     for(int i = 0; env[i]; i++)
     {   
         printf("env[%d]: %s\n", i, env[i]);
     }   
     return 0;
 }

我们来看一下运行结果:

QQ20250518-233756

可以看到运行结果与我们使用env命令是一样的,这也说明这些表是由父进程bash传递给我们的。那么main函数是怎么做到可以不传参数,也可以传两个参数,可以传三个函数的呢?前面我们说过,对于系统来说,main并不是第一个执行的函数,main函数实际上也会被调用,在Linux中main函数会被_start函数调用,我们可以简单模仿一下_start函数调用main函数的原理:

QQ20250518-234456

也就是说在我们写main函数时带的参数会被编译器识别。

因为我们的环境变量是继承的父进程的环境变量,所以环境变量是可以被子进程继承的。

2. getenv函数

QQ20250518-235155

它可以根据环境变量的名字获得相应的内容。我们来看一下演示:

 #include <stdio.h>
 #include <stdlib.h>
 int main()
 {
     char *value = getenv("PATH");
     if(value == NULL) return 1;
     printf("PATH->%s\n", value);
     return 0;
 }

我们来看一下运行结果:
QQ20250518-235725

可以看到,getenv可以得到我们指定的环境变量的值。

为什么环境变量要被子进程继承呢?这是为了子进程能够根据环境变量来做一些个性化操作。

3. environ全局变量

environlibc中定义的全局变量,它指向环境变量表,environ没有包含在任何头⽂件中,所以在使⽤时要⽤extern声明。 下面让我们看一下代码演示:

#include <stdio.h>

extern char **environ; // 声明 environ 作为外部变量

int main() 
{
    for (int i = 0; environ[i] != 0; i++) {
        printf("environ[%d]->%s\n", i, environ[i]); 
    }
    return 0;
}

看一下运行结果:
QQ20250519-001623

7. 变量和内建命令

7.1 变量

我们在shell中也可以直接定义变量:

QQ20250519-001946

这是在命令行中定义本地变量,本地变量与环境变量不同,它不可以被子进程继承,只在bash内部使用。也就是说,bash中会记录两套变量:本地变量和环境变量。

我们可以通过set命令查看bash中的所有命令:

QQ截图20250519002508

bash中的许多本地变量也具有它特殊的用途。

7.2 内建命令

我们在使用export命令导入环境变量时是直接导入到了bash的环境变量中,但是我们知道,我们使用的这些命令一般都是bash的子进程,而且进程之间具有独立性,那么export是怎么做到把环境变量导入到bash中的呢?其实,export不是一般的命令,它是内建命令(built-in command),这种命令不需要创建子进程,而是让bash自己去执行,也就是bash调用系统调用去完成。

echocdexporthistory这些都是内建命令,这些命令在 shell 启动时加载,并且可以直接在命令提示符下使用,而无需查找外部可执行文件。

尾声

本章讲解就到此结束了,若有纰漏或不足之处欢迎大家在评论区留言或者私信,同时也欢迎各位一起探讨学习。感谢您的观看!


网站公告

今日签到

点亮在社区的每一天
去签到