一、概念
环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数,是系统提供的一组name/value形式的变量,相当于给系统或用户应用程序设置的一些参数。
每个环境变量都分别有自己不同的用途。我们先来认识其中一个环境变量:PATH,并通过这个变量来简单了解一下环境变量的作用。
二、环境变量PATH
在Linux中,我们直接输入命令就可以执行,但是如果要运行一个程序,则需要带上路径
例如我们运行当前路径下的程序时,就需要加上相对路径才能运行
如果不加路径,就会报错,提示"command not found"
这说明命令在执行的时候是需要被查找的,在哪里查找呢?实际上就是在环境变量PATH的路径下查找。
我们可以通过echo $PATH查看PATH环境变量的内容:
可以看到PATH实际上就是许多个路径,当我们运行命令时,系统就会在这些路径下查找是否有对应的命令,如果有则执行。
进行猜想:如果我们将一个路径添加到PATH环境变量中,是否能够像运行命令一样来运行该路径下的程序呢?
首先我们将PATH的内容修改一下:
当我们修改PATH时,会将原来的值覆盖,如果我们想实现在原来的值后面添加一个路径,则需要在路径前面加上$PATH
现在再查看一下PATH环境变量的内容,可以看到我们的路径已经添加进去了:
接下来,我们通过export PATH命令将修改后的PATH重新加入环境变量中,然后我们就可以通过命令的方式来运行我们的二进制程序了:
需要知道的是,修改PATH环境变量只在当前用户下有效,并且当重启终端后就会复原,因为这种对环境变量的修改是临时性的。
三、Linux中的环境变量
Linux中常见的环境变量有:
- PATH:指定系统查找可执行文件的路径
- HOME:指定用户的主工作目录
- SHELL:指定当前用户默认使用的Shell
- USER:当前用户名
- LANG:系统的默认语言
- ......
如果我们想查看所有的环境变量,可以使用env命令
四、环境变量相关操作
4.1 unset
除了前面提到的echo、export和env,如果我们想取消一个环境变量可以用unset命令
例如我们随便添加一个环境变量:
要想删除这个环境变量,通过unset + 变量名就可以删除了
4.2 getenv
getenv是一个C库函数,以下是它的声明
char *getenv(const char *name)
这个函数可以传入一个环境变量名,返回该变量名的值,例如我们运行以下代码:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main()
{
char name[40];
strcpy(name, getenv("USER"));
if(strcmp(name, "root") == 0)
{
printf("root用户\n");
}
else
{
printf("普通用户\n");
}
return 0;
}
可以看到我们向getenv函数中传入了环境变量USER,所以它会返回当前用户名。
运行代码,结果如下:
4.3 main函数传参
(1)命令行参数
main函数也是函数,只要是函数就会被调用,也可以传入参数,所以我们的main函数实际上也是可以带参的:
int main(int argc, char *argv[])
{
}
这两个参数就是main函数的命令行参数。
但是main函数中的这两个参数是什么意思呢?
首先可以观察到,argv是一个指针数组,存放的是char*类型的指针,所以可以知道这个数组用于存放字符串。
而另一个参数argc,实际上就是数组argv中存放的元素数量。
现在,我们来观察一下argv中到底存放了什么东西,运行下面的这段代码
#include <stdio.h>
int main(int argc, char *argv[])
{
int i = 0;
for(;i < argc;i++)
{
printf("argv[%d]: %s\n", i, argv[i]);
}
return 0;
}
结果如下:
可以看到此时argv数组中存放了我们输入的命令字符串
我们知道,在输入命令的时候是可以带一些选项的,如果我们在运行自己的二进制程序时也带一些选项的话,结果就会变成这样:
通过观察我们就能发现,在输入命令时,如果我们的命令中带有选项,那么就会被打散成多个字符串,而argv数组中存放的就是这些字符串的地址,并且最后一个位置是NULL。
因为我们在Windows下不用通过命令来运行代码,所以平时我们基本用不到命令行参数。但是在Linux下这两个参数就有它们的用途了,例如:
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{
if(argc != 2)
{
printf("%s -[a|b|c|d]\n", argv[0]);
return 0;
}
else if(strcmp(argv[1], "-a") == 0)
{
printf("function 1\n");
}
else if(strcmp(argv[1], "-b") == 0)
{
printf("function 2\n");
}
else if(strcmp(argv[1], "-c") == 0)
{
printf("function 3\n");
}
else if(strcmp(argv[1], "-d") == 0)
{
printf("function 4\n");
}
return 0;
}
运行代码,结果如下:
可以看到,当我们输入不同的选项时,就可以执行不同的功能了,这就是命令行参数的用途。
命令行参数可以为指令、工具、软件等提供命令行选项的支持。
命令行参数不只是C/C++需要支持,几乎所有的语言都需要支持命令行参数,因为用这些语言写出来的软件都需要根据选项来定制化不同的功能。
(2)环境变量表
实际上,main函数中不止能传两个参数,还有第三个参数——环境变量表
int main(int argc, char *argv[], char *env[])
{
}
env和argv一样,都是一个存放char*类型的指针数组,而通过它的名字我们就可以知道其中存放的就是环境变量。通过下面这段代码进行验证:
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[], char *env[])
{
int i = 0;
for(; env[i]; i++)
{
printf("env[%d]: %s\n", i, env[i]);
}
return 0;
}
结果如下:
可以看到,env中的环境变量和我们输入env命令打印出来的环境变量是一样的。
当我们运行二进制程序时,这个进程就是bash的子进程。那么是否可以认为子进程可以继承父进程的环境变量呢?
实际上我们运行的进程,都是bash的子进程,而bash在启动的时候会从操作系统的配置文件中读取环境变量信息。在创建子进程的时候,也会通过传参等方式将环境变量继承给子进程。
环境变量通常具有全局属性,可以被子进程继承下去。除了环境变量,还有不具有全局属性的本地变量,例如:
这种变量是无法被子进程继承的,也不会存在于环境变量表中。在命令行中直接定义的变量就叫做本地变量。
如果我们想把本地变量存到环境变量表中,用export命令就可以了
前面说过,子进程会继承父进程的环境变量信息,那么我们再运行一次程序试试:
可以看到,当前我们的程序就已经拿到了新增的VAL环境变量了
现在我们用unset命令把这个环境变量删除,并重新将其设置为一个本地变量。
4.4 常规命令与内建命令
前面提到过,子进程无法继承父进程的本地变量,只能继承环境变量
而我们在Linux中输入的命令实际上也是bash的子进程,所以理应也无法读取到bash的本地变量
但是为什么我们用echo命令还能打印出bash的本地变量呢?
其实并不是所有的命令都要创建子进程,Linux中分为两种命令:
- 常规命令:通过创建子进程完成
- 内建命令:不创建子进程,而是由bash亲自执行
例如cd命令如果通过创建子进程来完成,由于进程的独立性,它只会修改子进程的工作路径。但是实际上是父进程的路径发生更改,因此cd其实也是一个内建命令。
4.5 第三方变量environ
我们不一定要通过传参的方式来获取环境变量表,还可以通过environ——一个指向父进程的环境变量表的指针,来获取环境变量
#include <stdio.h>
#include <unistd.h>
int main()
{
extern char** environ;
int i = 0;
for(; environ[i]; i++)
{
printf("%d : %s\n", i, environ[i]);
}
return 0;
}
environ是在libc中定义的全局变量,没有包含在任何头文件中,所以在使用时需要用extern声明
运行代码,结果如下:
如有错误或遗漏欢迎在评论区指出
完.