目录

一、概念介绍

1.1 环境变量的基本概念

1.2 命令行参数的介绍

1.3 查看环境变量

二、如何添加环境变量

2.1 PATH环境变量

2.2 其他环境变量

2.2.1 HOME环境变量

2.2.2 SHEll环境变量

2.2.3 USER和LOGNAME环境变量

2.2.4 HISTSIZE环境变量

2.2.5 PWD环境变量

2.2.6 HOSTNAME环境变量

2.2.7 OLDPWD环境变量

​2.3 总结

三、获取环境变量方法

3.1 如何增加一个环境变量

3.2 如何删除一个环境变量

3.3 在代码中获取环境变量

3.3.1 方法一:使用env参数

3.3.2 方法二:使用getenv函数(推荐)

3.3.3 方法三:使用environ全局变量

四、理解环境变量的特性

五、总结

5.1 环境变量概念总结

5.2 具体的环境变量总结

5.3 命令总结


环境变量是一种非常重要的概念,它对于系统的行为和应用程序的运行起着关键的作用。

那么环境变量究竟在扮演着什么样的角色呢?
为什么我们在编写C/C++代码的时候,不需要显式的告诉编译器我们所需要的动静态库在那,但链接照样可以成功的生成可执行程序,其实这里就有环境变量的辅助,所以接下来,就让我们一起来学习 ----- 环境变量

一、概念介绍

1.1 环境变量的基本概念

  • 环境变量(environmentvariables)一般是指在操作系统中用来指定操作系统运行环境的一些参数
  • 如:我们在编写C/C++代码的时候,在链接的时候,从来不知道我们的所链接的动态静态库在哪里,但是照样可以链接成功,生成可执行程序,原因就是有相关环境变量帮助编译器进行查找。
  • 环境变量通常具有某些特殊用途,还有在系统当中通常具有全局特性

1.2 命令行参数的介绍

首先,我们通过一个问题来引出:

main是一个函数,main函数有参数嘛?

答案是:main是有参数的!!!main可以有0个参数或者2个参数,或者3个参数!!

我们平常写main函数的时候都没有写main函数的函数。

当main函数有两个参数的时候,这两个参数分别是什么呢?

答案是int类型的argc和char*类型的argv。其中,argv是一个指针数组,里面存放了字符串的地址,叫做命令行参数表。而argc是argv中的元素个数。那argv里面存放的是什么呢?

我们写一个测试代码来看一下,如下所示:

  1 #include <stdio.h>
  2 
  3 int main(int argc,char* argv[])
  4 {
  5     for(int i=0;i<argc;i++)
  6     {
  7         printf("argv[%d]=%s\n",i,argv[i]);
  8     }
  9     return 0;
 10 }

我们使用gcc来编译:gcc code1.c -o code1

然后使用./code1来运行,如下所示:

我们发现argv里面存放的是我们刚刚输入的命令,也就是将字符串./code的地址存到了argv数组里面。如果我们输入./code1 a b c d呢?如下所示:

我们发现argv里面存放的是的我们刚刚输入的命令,以空格来区分字符串,将我们输入的命令存储下来。如下所示:

当我输入命令的时候,xshell,也就是bash会帮我们把命令以空格区分,将这几个字符串的指针存储到agrv数组中。并且argv是以null结尾的,里面的元素个数有argc个。

这个code1是我们编写的程序,我们运行它需要带上./,通过相对路径来找到这个程序,那为什么我们使用系统命令的时候,如:ls pwd  cd 这些命令不需要带上路径呢?系统提供给我们的命令本质上不就是程序吗?和我们写的code1本质上是没有任何区别的,为什么我们写的程序运行的时候需要加上路径,而系统提供给我们的命令不需要加上路径呢?如下所示:

我们在使用系统提供给我的命令是不需要带上路径的,也可以带上,使用我们自己编写的程序需要带上./来找到程序,这是为什么呢?

那这个跟我们的环境变量有什么关系呢?

那main函数的命令行参数有什么用呢?

首先我们来介绍一个知识点,我们在使用ls的时候是可以带上选项的,例如ls -a -l等等。

系统提供的命令都是c语言写的,我们如何实现这个带选项的程序呢?

代码如下所示:

#include <stdio.h>
#include <string.h>

int main(int argc,char* argv[])
{
	if(argc!=2)
	{
		printf("Uages:%s [-a|-b|-c]\n",argv[0]);
		return 1;
	}
	if(strcmp(argv[1],"-a")==0)
	{
		printf("这是功能1\n");
	}else if(strcmp(argv[1],"-b")==0)
	{		
		printf("这是功能2\n");
	}else if(strcmp(argv[1],"-c")==0)
	{		
		printf("这是功能3\n");
	}else
	{		
		printf("Uages:%s [-a|-b|-c]\n",argv[0]);
	}
	return 0;
}

运行如下所示:

代码解析:

当我们输入./code2来执行我们写的程序的时候,是没有任何逻辑的,这是因为这个程序需要带上选项,所以提示Uages:./code2 [-a|b|-c]。

当我们输入./code2 -a的时候就运行功能1的逻辑

当我们输入./code2 -b的时候就运行功能2的逻辑

当我们输入./code2 -c的时候就运行功能3的逻辑

当我们输入./code2 -d的时候,就提示Uages:./code2 [-a|b|-c]这样使用。

我们以前用到的ls -a,ls -l ,rm -r 等等,都带了选项。所以命令行参数的用途,是为了让一个程序可以通过选项来执行不同的子功能。

所以main函数的命令行参数,是实现程序不同子功能的方法!!!

这就是指令可以带选项的原理,main函数是可以有参数的!!!

所以在我们程序运行的时候,进程启动时,进程拥有一张argv表,用来支持实现选项功能。是bash帮忙创建的。

现在我们来回答这两个问题:

我们在使用系统提供给我的命令是不需要带上路径的,也可以带上,使用我们自己编写的程序需要带上./来找到程序,这是为什么呢?那这个跟我们的环境变量有什么关系呢?

我们自己写的程序和系统提供的指令,都是c语言代码,没有本质的区别!!

我们在执行一个程序的时候,必须要找到这个程序在哪里!所以我们在运行我们自己的程序的时候需要./来找到程序。为什么执行系统命令的时候不需要加上路径,只需要执行指令就可以呢?

这是因为系统中存在环境变量!!!环境变量会帮我们找到系统指令的位置!!!

我们在执行我们的程序的时候,不会在当前路径下查找,而是会在/usr/bin目录下查找,那我们将我们写的程序拷贝到/usr/bin目录下是不是也可以不要带路径了呢?

答案:是的,当我们将我们的程序拷贝到/usr/bin目录下,执行我们的程序就不用带上路径了

如下所示:

我们将code2程序拷贝到/usr/bin目录下之后,直接输入程序名字就可以执行了。

我们强烈不建议将我们写的程序拷贝到/usr/bin目录下,这是因为我们写的程序可能是有bug的,把我们写的程序放在系统的默认路径下,就可能污染系统的指令池。所以我们将/usr/bin目录下code2程序给删除。然后再次输入程序名字就找不到程序了,需要输入./才能找到程序。如下所示:

最终结论就是:我们系统是默认知道/usr/bin目录下的,那为什么系统会知道在/usr/bin目录下查找程序呢?

这是因为系统中存在环境变量,这个环境变量叫做PATH。

总结:在linux系统中,存在一个环境变量PATH,PATH会标识一段路径,当我们执行程序的时候,会默认在环境变量PATH路径下查找!!!所以PATH是linux指令和程序执行时的搜索路径!!!

1.3 查看环境变量

我们在上面将PATH环境变量引出来之后,那我们如何查看环境变量呢?

我们可以使用env命令来查看环境变量,可以将系统中所有的环境变量给我罗列出来,如下所示:

环境变量是一个变量,环境变量的构成是:名字=内容,通过名字来标识环境变量的唯一性!

这个环境变量也太多了,我们就想查看特定的PATH环境变量,如何操作呢?

我们可以使用echo $PATH来查看PATH环境变量,如下所示:

PATH输出的这个长字符串以:(分号)为划分点,分为好几条路径:如下所示:

  1. /usr/local/sbin
  2. /usr/local/bin
  3. /usr/sbin
  4. /usr/bin
  5. /sbin/bin
  6. /usr/games
  7. /usr/local/games
  8. /snap/bin

所以我们在linux系统执行命令时,就默认在这些路径下搜索,这也就解释了为什么我们写的程序需要加上./,而系统提供的命令不需要加上./,因为默认会在这些路径下查找,从1到9,依次查找!

如果所有的PATH环境变量路径下都没有找到程序,就会报command not found错误。

正是因为ls命令在/usr/bin目录下,所有我们在执行ls指令的时候不需要带路径,默认会在PATH路径下查找!!

那我们将我们自己的路径添加到PATH环境变量里面,那我们执行我们自己的程序,就不是不需要带./了嘛?

没错,只要我们将我们自己的路径添加到PATH环境变量中,那这个路径下的程序不需要带./就能执行。那我们如何将我们自己的路径添加到PATH环境变量中呢?如下:

二、如何添加环境变量

2.1 PATH环境变量

系统中已经有PATH环境变量了,我们可以直接PATH=路径,如下所示:

PATH=/home/zhang/linux113/lesson16

此时我们再次查看PATH环境变量,就为这个路径了/home/zhang/linux113/lesson16,如下所示:

我们执行我们自己的程序就不用带./了。

但是我们执行系统指令的时候,如下所示:

就会报command not found错误了。这是因为PATH环境变量修改了,我们在/home/zhang/linux113/lesson16路径下找不到系统指令了。

这也反向的证明了linux系统在查找命令时就是在PATH命令下查找的。

我们想让我们的路径添加到PATH路径中,而不是覆盖原来的路径,那怎么办呢?现在原来的路径都没有了。如何恢复呢?

我们可以直接关掉xshll,然后重新登录,此时PATH环境变量就恢复成原来的样子了,如下所示:

此时PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin,只需要关闭xshell,在重新登录,PATH路径就自动恢复了。

现在,我们只想让PATH添加/home/zhang/linux113/lesson16,而不是覆盖,如下所示:

我们先将PATH打印出来复制一份,然后在赋值的时候将拷贝的内容粘贴,在后面加上:/home/zhang/linux113/lesson16,就可以了,我们就可以在PATH环境变量中添加我们的当前的路径了,执行code2也就不需要带./了。

然后我们再将xshell关闭,再重新登录,就发现又恢复成原来的样子了,没有我们当前了路径了。

所以当我们重新登录xshell时,我们之前自己配置的环境变量都会恢复默认。

那么现在又有两个问题了,

1:从存储的角度,如何理解环境变量呢?

2:系统重新登陆,环境变量最开始从哪里来呢?

答1:实际上,环境变量是由名称+内容组成的。但是这些环境变量的值是被谁保存下来的呢?包括当我们在执行指令时,是谁在系统中查找这些命令呢?答案是bash,当我们登录的时候,系统就会给我们创建一个bash进程,而bash就必须在系统中读取我们的环境变量信息,然后在bash进程内部形成一张表,这张表叫环境变量表,也就是指针数组的样子,其实这些环境变量,一个一个的字符串,环境变量表存放字符串的地址,如下所示:

所以在bash进程启动的时候,需要构建出来一个环境变量表。然后我们输入指令时,例如ls -a。ls -a这个字符串就会被bash进程拿到,然后bash会帮忙拆分这个字符串,进而构建一个命令行参数表argv,得到程序的名字,然后在PATH路径中查找ls程序是否存在!!如果存在,就创建ls子进程、不存在就报错。

所以bash进程内部有两张表,一个是环境变量表,一个是命令行参数表。

所以环境变量就是Key/Value的常字符串,当bash启动的时候,会new一段空间来创建环境变量表来存放这个常字符串!!!bash进程会维护这张表,当执行env的时候,就打印这张表的内容,因此环境变量在bash进程的上下文里面。

答2:环境变量是从系统的相关配置文件中来的,linux系统是会存在系统的相关配置文件的。

这个配置文件会直接或间接包含所有的环境变量内容,当bash进程启动的时候。他会从配置文件中读取所有的环境变量值,并在自己的内部创建这一张表。如果我们将我们自己的目录路径系统的配置文件中,那么每次重新登录的时候,bash也会将我们的路径添加到PATH路径中。

最后,如果我们linux系统有10个用户在登陆,那么应该有几个bash进程呢?

答案是,如果我们linux系统有10个用户在登陆,就会有10个bash进程,并且每一个bash都会读取系统的相关配置文件,形成环境变量表,也就是10份环境变量表。

2.2 其他环境变量

在上面我们介绍了PATH环境变量,其实linux还有其他的环境变量,如下所示:

我们挑几个终于的环境变量讲解

2.2.1 HOME环境变量

我们在执行cd ~的时候,会回到当前用户的家目录,这是为什么呢?

这是因为环境变量表中有HOME,记录了当前用户的家目录,如下所示:

当一个用户登录的时候,会创建一个bash进程,然后将HOME环境变量设置为/home/zhang。当bash进程收到 cd ~时,会切换成为cd /home/zhang。所有HOME环境变量叫做当前用户的家目录

2.2.2 SHEll环境变量

这个环境变量指的是,用户在登录时,bash使用的时哪一个版本的shell,所以把bash的路径保持下来了。如下所示:

2.2.3 USER和LOGNAME环境变量

USER环境变量指的是当前用户是谁。LOGNAME环境变量指的时当前登录用户是谁。

我们在使用su和su -切换root身份的时候,这两个会有所不同。

当我们是zhang这个用户,然后使用su切换为root的身份,此时USER和LOGNAME环境变量都还是zhang,如下所示:

我们使用su切换为root身份的时候,USER和LOGNAME环境变量都还是zhang,并且当前路径不会发生任何变化。

当我们是zhang这个用户,然后使用su -切换为root的身份,此时USER和LOGNAME环境变量都会变成root,这是因为我们使用root的省份重新登录了,如下所示:

我们使用su -切换为root身份的时候,会让root重新登录。USER和LOGNAME环境变量都会变成root,并且当前路径变成了/root。

2.2.4 HISTSIZE环境变量

我们在xshell命令行上下翻,就能查看历史命令,其实是bash会将我们历史输入的指令给记录下来,并不能记录太多,所以设置了HISTSIZE环境变量=1000,只能记录最近的1000条命令。并且使用history就会得到历史输入的命令。如下所示:

2.2.5 PWD环境变量

PWD环境变量会记录当前的路径,如下所示:

当我们执行pwd指令的时候,就会输出PWD环境变量的值。

2.2.6 HOSTNAME环境变量

HOSTNAME环境变量会记录登录的主机名,如下所示:

2.2.7 OLDPWD环境变量

OLDPWD环境变量会记录前一个路径,每次我们使用cd -的时候,都会回到OLDPWD的路径,如下所示:

2.3 总结

  • 环境变量(environmentvariables)一般是指在操作系统中用来指定操作系统运行环境的一些参数,被bash使用,
  • 如:我们在编写C/C++代码的时候,在链接的时候,从来不知道我们的所链接的动态静态库在哪里,但是照样可以链接成功,生成可执行程序,原因就是有相关环境变量帮助编译器进行查找。
  • 环境变量通常具有某些特殊用途,还有在系统当中通常具有全局特性
  1. PATH:命令搜索路径,执行程序时依次在其内目录查找可执行文件;临时添加PATH=$PATH:路径,仅当前终端有效。
  2. HOME:当前用户家目录,cd ~依赖此变量。
  3. USER/LOGNAME:USER 为当前操作用户,LOGNAME 为初始登录用户;su -会刷新二者,su不改变。
  4. SHELL:当前使用的解释器路径,默认 /bin/bash。
  5. PWD:当前工作目录,pwd 命令读取该值。
  6. OLDPWD:上一次工作目录,cd -跳转依靠它。
  7. HISTSIZE:shell 保存历史命令最大条数。
  8. HOSTNAME:本机主机名。
  9. 编译相关
    • LIBRARY_PATH:编译找库
    • LD_LIBRARY_PATH:运行加载动态库
    • C_INCLUDE_PATH:编译器查找头文件

基础特性

格式:变量名=值;终端直接赋值为临时变量,重启终端失效;子进程会继承环境变量。

查看命令

env查看全部,echo $变量名单独查看指定变量。

三、获取环境变量方法

  1. echo: 显示某个环境变量值
  2. export: 设置一个新的环境变量
  3. env: 显示所有环境变量
  4. unset: 清除环境变量
  5. set: 显示本地定义的shell变量和环境变量

3.1 如何增加一个环境变量

在上面我们是修改现有的环境变量,如果我们想要增加一个环境变量呢?

我们可以使用export命令,如下所示:

3.2 如何删除一个环境变量

可以使用unset命令,如下所示:

3.3 在代码中获取环境变量

在上面我们都是使用命令的方式获取环境变量,如果我们想要在我们写的c语言代码中获取环境变量,那应该怎么做呢?

3.3.1 方法一:使用env参数

我们回到1.2部分中,我们说了main是有参数的!!!main可以有0个参数或者2个参数,或者3个参数!!

并且通过代码介绍了main函数有两个参数的例子,其实main参数可以有三个参数,那么这三个参数分别是什么呢?

答案是int类型的argc,char*类型的argv和char* 类型的env。在上面我们介绍了int类型的argc,char*类型的argv,接下来我们来介绍env。

在main函数中,谁调用main函数,就会传入3个变量,分别是int类型的argc,char*类型的argv和char* 类型的env。一般都是父进程传入这三个变量给子进程的。

我们写的代码编译成为可执行程序,然后运行,它的父进程其实是bash,在上面说过,bash进程本来就有两张表,一个叫命令行参数表,一个叫环境变量表。bash会做命令行解析,并且将解析好的命令通过命令行参数表传递给子进程,环境变量表在登录的时候就创建好了,也可以叫环境变量表传递给子进程。

测试代码如下:

#include <stdio.h>

int main(int argc,char* argv[],char* env[])
{
	(void)argc;
	(void)argv;
	for(int i=0;env[i];i++)
	{
		printf("env[%d]->%s\n",i,env[i]);
	}
	return 0;
}

为什么我们需要加这两行代码嘛?(void)argc;(void)argv;

因为,我们在main函数中定义了它们,但是没有使用,编译会弹出警告,所以使用这种方式使用一下,让编译器不弹出警告!

运行如下所示:

么这个环境变量是从哪里获取的呢?

答案是从父进程那里获取的,也就是bash,环境变量可以被子进程继承,也可以被子进程的子进程继承,因此,bash进程下面的所有进程都能够得到环境变量,所以环境变量是具有全局特性的,如果我们在当前bash进程增加几个环境变量,然后再次运行代码,运行结果也会发生变化,这也充分证明了main函数里面的env指针数组是从父进程那里获取的。如下所示:

3.3.2 方法二:使用getenv函数(推荐)

我们使用man来查看一下getenv的使用,如下所示:

他的作用是获得指定环境变量的地址,如果没有返回null。

测试代码如下:

#include <stdio.h>
#include <stdlib.h>

int main(int argc,char* argv[],char* env[])
{
	(void)argc;
	(void)argv;
	(void)env;
	char* value=getenv("PATH");
	if(value==NULL) return 1;
	else
	{
		printf("PATH->%s\n",value);
	}
	return 0;
}

编译运行如下所示:

现在我想让我写的代码只允许我一个人用,其他的人都不能用,那我们应该如何写代码呢?

在当前系统中,我们只有bash知道当前用户是谁,这是因为bash中记录了用户是谁,

代码如下所示:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc,char* argv[],char* env[])
{
	(void)argc;
	(void)argv;
	(void)env;
	const char* who=getenv("USER");
	if(who==NULL)
	{
		return 1;
	}
	if(strcmp(who,"zhang")==0)
	{
		printf("这是程序的正常逻辑\n");
	}else
	{
		printf("only zhang\n");
	}
	return 0;
}

运行如下所示:

为什么环境变量要被子进程继承呢?

因为我们把环境变量继承下去,子进程就可以结合环境变量来做个性化操作。

如果子进程想要修改,会发生写时拷贝的。

3.3.3 方法三:使用environ全局变量

我们使用man来查看一下environ的使用,如下所示:

environ的类型是一个二级指针,这是因为它指向环境变量表,环境变量表里面存放的是char*类型的指针,所以environ的类型是char**。

测试代码如下:

#include <stdio.h>
#include <unistd.h>

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

编译运行如下所示:

四、理解环境变量的特性

在上面我们说了环境变量具有全局属性,在子进程里面也可以使用。

我们还可以定义本地变量,如下所示:

可以使用set命令来查看本地变量和环境变量,这个本地变量不会被子进程继承,只在bash内部使用。使用env只能查看环境变量,不能查看本地变量

我们定义了本地变量i之后,还可以使用export i 将本地变量导成环境变量,如下所示:

我们使用export导入环境变量,这个环境变量是导入到谁里面了?

环境变量是被导入到bash里面了,bash的环境变量可以被子进程来继承,但是我们使用export来导入环境变量,本质上不是应该是bash创建了一个export的子进程,这个子进程来导入环境变量嘛?但是子进程是如何将变量导入到bash进程里面,放到bash进程的环境变量表中呢?进程之间是有独立性的,父进程可以将环境变量给子进程,子进程是不能将数据给父进程的。所以export这种命令一旦被执行,怎么可能将环境变量给bash呢?

答案是:export这种命令和其他的命令不一样,export叫做内建命令,这个命令在执行的时候不需要创建子进程!!而是由bash自己去执行。

五、总结

5.1 环境变量概念总结

  • 环境变量(environmentvariables)一般是指在操作系统中用来指定操作系统运行环境的一些参数,被bash使用,
  • 如:我们在编写C/C++代码的时候,在链接的时候,从来不知道我们的所链接的动态静态库在哪里,但是照样可以链接成功,生成可执行程序,原因就是有相关环境变量帮助编译器进行查找。
  • 环境变量通常具有某些特殊用途,还有在系统当中通常具有全局特性

5.2 具体的环境变量总结

  1. PATH:命令搜索路径,执行程序时依次在其内目录查找可执行文件;临时添加PATH=$PATH:路径,仅当前终端有效。
  2. HOME:当前用户家目录,cd ~依赖此变量。
  3. USER/LOGNAME:USER 为当前操作用户,LOGNAME 为初始登录用户;su -会刷新二者,su不改变。
  4. SHELL:当前使用的解释器路径,默认 /bin/bash。
  5. PWD:当前工作目录,pwd 命令读取该值。
  6. OLDPWD:上一次工作目录,cd -跳转依靠它。
  7. HISTSIZE:shell 保存历史命令最大条数。
  8. HOSTNAME:本机主机名。
  9. 编译相关
    • LIBRARY_PATH:编译找库
    • LD_LIBRARY_PATH:运行加载动态库
    • C_INCLUDE_PATH:编译器查找头文件

5.3 命令总结

  1. echo: 显示某个环境变量值
  2. export: 设置一个新的环境变量
  3. env: 显示所有环境变量
  4. unset: 清除环境变量
  5. set: 显示本地定义的shell变量和环境变量
Logo

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

更多推荐