1. 环境变量的概念
环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数,是用于存储系统和程序运行时所需的配置信息的变量。
这些变量可以在shell中设置,并且可以被程序和脚本访问。环境变量通常具有某些特殊用途,还有在系统当中通常具有全局特性。
如:我们在编写C/C++代码的时候,在链接的时候,从来不知道我们的所链接的动态静态库在哪里,但是照样可以链接成功,生成可执行程序,原因就是有相关环境变量帮助编译器进行查找。
常见的环境变量:
PATH : 指定命令的搜索路径(在Linux中通常为/user/bin)
HOME : 指定用户的主⼯作目录(即用户登陆到Linux系统中时,默认的目录)
SHELL : 当前Shell(在Linux和Unix操作系统中,它的值通常是/bin/bash)
以"PATH"为例:
我们会发现,自己写的可执行程序在运行时需要带上可执行文件的路径,而系统中的指令(本质上也是可执行程序)却不需要,这就是因为:当用户需要执行某个可执行程序时,系统会到"PATH"指定的目录中去寻找,而不会在当前目录中寻找,并且系统中的指令都被放到了该目录下。
如果我们将自己写的程序放到"PATH"指定的目录下,在运行时就不需要指定路径了,这也是Linux下软件安装的本质:将可执行程序拷贝到"PATH"指定目录下。
2. 环境变量的有关指令
- [echo $环境变量名]:显示某个环境变量的值。
- [export 新环境变量名=值]:设置一个新的环境变量。
- [env]:显示所有环境变量。
- [unset 环境变量名]:清除环境变量。
- [set]:显示本地定义的shell变量和环境变量。
以"echo $HOME"为例:
2.1 设置永久的环境变量
以上指令设置的环境变量都是临时的,是用户在登录时被加载到shell中的一份拷贝,如果我们退出重登,这些环境变量都会被重置。
那么如何设置永久的环境变量呢?要做到这一点,我们首先要搞清楚shell中的拷贝是从哪里来的。
我们知道,Linux中一切皆文件,所以系统中的一切初始化都是通过配置文件完成的。在用户的家目录下会有环境变量的配置文件,我们只需要修改这个文件中的内容即可设置永久的环境变量。
编辑用户级别的配置文件:
- 对于Bash shell,可以编辑
~/.bashrc
或~/.bash_profile
文件。例如,打开~/.bashrc
文件:nano ~/.bashrc
- 添加环境变量:
export MY_VARIABLE="Hello, World!"
- 由于当前bash已经经过初始化,所以需要输入以下指令使更改生效:
source ~/.bashrc
编辑系统级别的配置文件:
- 打开
/etc/profile
文件(需要管理员权限):sudo nano /etc/profile
- 添加环境变量:
export MY_VARIABLE="Hello, World!"
- 使更改生效:
source /etc/profile
2.2 shell变量
除了环境变量,还可以在shell中定义本地变量:
使用[set]指令可以查看所有变量(环境变量和shell变量)。
3. 环境变量的组织方式
环境变量是以什么形式加载到shell中的呢?或者说环境变量是以什么形式加载到内存中的?
环境变量其实是以字符指针数组的形式加载到内存中的,我们称其为环境变量表,如下图所示:
每个环境变量都是以"名称=内容"的字符串的形式存在的,且环境变量表一定以"NULL"结尾。
程序在运行时都会收到一张环境变量表,且子进程会继承父进程的环境变量表,并在发生写入时进行写时拷贝。这种继承机制使得环境变量在整个程序执行期间以及在派生的子进程中保持一致性和可用性。
bash本身也是一个进程,通过bash启动的进程都是其子进程,继承的都是这bash的环境变量表。
正因为如此,系统中的指令才能访问到这张表,"pwd"、"cd ~"("~" = "HOME")等指令才能工作。
至于程序是如何接收到这张表并利用起来的,我们这就开始介绍。
4. 代码层面访问环境变量
在代码层面访问就会涉及到对环境变量的修改,正如我们之前说所,父子进程默认共享一张环境变量表,但是有写时拷贝的机制存在,以确保进程的独立性。
因此,我们要注意一点:对环境变量的修改只会作用于当前进程或其子进程,不会影响其父进程。
4.1 main函数的参数
在代码层面上看,main函数是我们程序的入口;但在操作系统层面上看,main函数并不是整个进程的入口,main函数也是被调用的。
所以,main函数实际上也是有参数的(在很多教科书上都有带上参数或用void表示没有参数),考虑参数的话,main函数有以下三种写法:
int main(){}
int main(int argc, char *argv[]){}
int main(int argc, char *argv[], char *env[]){}
4.1.1 argc与argv
- argc:argv数组的元素个数。
- argv:命令行中读入的参数。
以下面的代码为例说明这两个参数的含义:
#include <stdio.h>
int main(int argc, char *argv[])
{
for(int i = 0; i < argc; i++)
{
printf("%s ", argv[i]);
}
printf("\n");
return 0;
}
所以,bash在读入命令行的指令之后,运行对应的程序并将命令行读入的指令以空格为分割作为字符串传递给main函数。
这就是指令能够通过选项对运行细节进行控制的原因。
4.1.2 第一种访问环境变量的方式:env参数
相信我不说大家都能猜得到,env就是环境表。由于规定了环境表的最后一个元素是NULL,所以不用再传入一个表示环境表大小的参数。
#include <stdio.h>
int main(int argc, char *argv[], char *env[])
{
for(int i = 0; env[i]; i++)
{
printf("%s\n", env[i]);
}
return 0;
}
那么env指令的原理不言而喻。这就是第一种在代码层面访问环境变量的方式。
4.2 全局变量environ
在Linux系统中,environ
是一个全局变量,它是一个指向环境变量表的指针。
在C语言程序中,可以通过extern char **environ;
来声明这个全局变量,然后通过遍历这个指针数组来访问所有的环境变量。
#include <stdio.h>
int main()
{
extern char **environ;
for (int i = 0; environ[i]; i++)
{
printf("%s\n", environ[i]);
}
return 0;
}
结果与上面相同,就不演示结果了。
4.3 系统调用函数
在Linux系统中,getenv
和putenv
是两个用于处理环境变量的函数,其原型都被定义在<stdlib.h>
头文件中。
4.3.1 getenv函数
getenv
函数用于获取环境变量的值。
char *getenv(const char *name);
- 参数:
name
是一个字符串,表示要查询的环境变量的名称。- 返回值:如果环境变量存在,则返回指向该环境变量值的指针;如果环境变量不存在,则返回
NULL
。
4.3.2 putenv函数
putenv
函数用于设置或更改环境变量的值。
int putenv(char *string);
- 参数:
string
是一个格式为"VAR=value"
的字符串,其中VAR
是环境变量的名称,value
是要设置的值。- 返回值:成功时返回0,失败时返回-1,并设置
errno
。
#include <stdio.h>
#include <stdlib.h>
int main() {
// 获取PATH环境变量的值
char *path = getenv("PATH");
printf("PATH: %s\n", path);
// 设置一个新的环境变量
int result = putenv("MYVAR=myvalue");
if (result == 0) {
printf("环境变量MYVAR设置成功\n");
} else {
perror("putenv");
}
// 获取新设置的环境变量的值
char *myvar = getenv("MYVAR");
printf("MYVAR: %s\n", myvar);
return 0;
}
这两个函数能够对指定的环境变量进行访问,避免了对环境变量表的遍历查找,且可读性更高,是三种方法中最推荐,最实用的。