什么是环境变量
- 从结构上来看,环境变量就是操作系统维护的一组:
key-value
的键值对。
不知道你是否有一个疑问:为什么我们写代码编译链接 形成的可执行程序要运行起来需要带路径呢?Linux 内置的命令也是可执行程序,为什么这些命令就不用带路径呢?
-
我们都知道,Linux 的系统指令是放在
/usr/bin
目录下的。/usr/bin
是系统命令的默认搜索路径,所以执行系统的指令不需要带路径! -
也就是说如果我们自己写的可执行程序也想不加路径直接用名称来运行,我们只要将可执行文件移动到
/usr/bin
目录下就可以啦!c#include<stdio.h> int main() { printf("hello environment\n"); return 0; }
mv test /usr/bin/__test
:将形成的test
可执行文件移动到/usr/bin
目录,并将文件重命名为__test
,那么这个__test
程序,就能够不加路径直接运行啦!
命令行获取环境变量
除了将可执行文件,放到系统指令的默认搜索路径,还有没有其他的办法呢?因为将可执行文件放到系统命令的默认搜索路径是不优雅的行为!我要是能将可执行文件所在的路径设置为系统命令的搜索路径,那也一样能做到嘛!
PATH 环境变量
事实上,系统命令的默认搜索路径可不止 /usr/bin
一个,可是有好多呢。PATH
环境变量里面全都是呢!那么怎么查看 PATH
环境变量中的内容呢?echo PATH
行嘛?不行的哦!直接 echo
PATH
会被解析成字符串的,要打印 PATH
的内容,需要加上 $
符号!
我们看到,PATH
的内容有如下特征:
- 由很多的路径组成。
- 路径与路径之间由冒号隔开。
PATH
不是系统命令的默认搜索路径嘛!那么我们怎么加入自己的目录呢?修改 PATH
内容的本质就是对 PATH
重新赋值!
那我们来看看将我们写的可执行文件所在的目录添加到 PATH
环境变量需要
-
首先我们获可执行文件所在的目录:
pwd
:
-
然后就是将这个目录添加到
PATH
环境变量中。思考🤔:PATH=/root/cppcpp/cpp/12_6_cpp_2
这么写是不是正确的呢?你这么做了,发现我们写的可执行文件也能直接运行,但是,原PATH
中的内容就不见了!想要新增目录,可以这么写:bashPATH=$PATH:/root/cppcpp/cpp/12_6_cpp_2
可以看到可执行文件所在的目录被添加到了
PATH
环境变量中:
直接运行可执行程序:__test
。
我们重新启动xshell
,然后打印PATH
环境变量中的内容,发现我们刚才添加的目录消失不见了!这是为什么呢? -
这是因为,我们每次启动
xshell
操作系统会在PATH
环境变量的配置文件中加载,给PATH
环境变量赋值。这就意味着,如果我们不修改PATH
环境变量的配置文件,再每次xshell
重新启动的时候,我们都要重新添加目录!这就非常麻烦!
我们想要一劳永逸的话就要修改 PATH
环境变量的配置文件。
-
当前用户环境变量的配置文件
cd ~
:进入当前用户的家目录。ls -al
:查看家目录下的所有文件。
-
.bashrc
与.bash_profile
文件修改这里面有两个文件:
.bashrc
和.bash_profile
。在这两个文件中,我们能够为当前用户配置属于自己的PATH
环境变量,也就是说配置这两个文件只会对当前用户生效!我们可以看到这两个文件中都有一段脚本代码(红框框的部分)。
- 在
.bash_profile
文件中表示,如果家目录下存在.bashrc
文件,就会执行这个文件。 - 同理,在
.bashrc
文件中表示,如果/etc/bashrc
文件存在那么就执行这个文件。
可以得出结论:环境变量的配置文件有很多,配置文件之间还存在调用关系。
想要为当前用户配置环境变量,修改这两个文件都可以(我们以修改
.bashrc
文件为例),有两种书写的方式:- `export PATH="$PATH:/root/cpp"
PATH=$PATH:/root/cppcpp
export PATH
修改.bashrc
文件之后,我们可以使用:source .bashrc
刷新一下配置文件,我们就能看到我们通过修改配置文件添加的环境变量了!
- 在
事实上,xshell
启动之后,操作系统加载环境变量的配置文件的顺序是:
bash
/etc/profile ----> ~/.bash_profile ----> ~/.bashrc ----> /etc/bashrc
这是 centos
的加载顺序,具体的 linux
操作系统可能会有差异,但是原理都是差不多的!
现在我们就能够理解,为什么我们在命令行窗口输入一个命令,如果不存在这个命令,报错的提示是:command not found
了!
- 这证明,执行一个命令,他会在
PATH
环境变量中搜索,是否存在这个变量!如果不存在,就是因为找不到嘛! - 当然,你也可以指定路径运行指令就不用在
PATH
环境变量中搜索了!
HOME 环境变量
这个环境变量的值就是登陆用户的家目录!
-
root 用户:
-
普通用户:
我们登陆
xshell
的时候,为啥默认就是在登录用户的家目录呢?事实上,登录
xshell
的时候,xshell
会识别到登录的用户是谁,然后填充HOME
这个环境变量。登录成功之后,xshell
分配bash
,执行类似cd
命令,进入登录用户的家目录!
SHELL
这个环境变量可以查看使用的是哪一个 shell
。
查看所有的环境变量
bash
env
env
命令可以查看 bash
进程从系统继承下来的环境变量。
可以看到这里面就有几个是我们刚才讲到的,这里在补充几个:
-
HOSTNAME:主机名字。我们在命令行中看到的命令行提示符的一部分就有主机名,他就是根据
HOSTNAME
环境变量获取的,然后打印到显示器上!
-
USER:这个环境变量是登录用户的用户名,在命令行提示符中就会用到!
-
PWD:这个环境变量保存的是当前进程所处的目录,
bash
不也是个进程嘛! -
LOGNAME:这个和
USER
可以认为是一样的,你可以通过切换用户来验证。 -
OLDPWD: 这个环境变量保存的是当前进程上一次处于那个目录,这就是
cd -
的原理啦! -
HITSIZE:我们上下键可以切换我们历史输入的命令,其实历史输入的命令是被保存到
~/.bash_history
中的!而这个HITSIZE
就是能够保存历史输入命令的最大条数!
函数获取环境变量
我们要是想要在我们自己写的程序中获取环境变量怎么办呢?
我们来看一个函数吧:char* getenv(const char* name)
记得包含头文件:#include<stdlib.h>
通过传入环境变量的 key
获取他的 value
。
我们可以写一个代码获取 PATH
环境变量
c
#include<stdio.h>
#include<stdlib.h>
int main()
{
printf("PATH=%s\n", getenv("PATH"));
return 0;
}
main 函数参数获取环境变量
在使用 main
函数的参数获取环境变量之前,我们先来学习一个知识点:命令行参数
命令行参数
什么是命令行参数
我们之前其实就一直在用命令行参数,就是不知道叫这个东东罢了!
这里的 -a
-l
选项就是命令行参数啦!
命令行参数是干什么的,有什么用?
命令行参数就是用来给某些函数传参的,这些系统指令是不是也是一个一个的进程!那么他的本质就是可执行程序,比如同一个 ls
命令,带上不同的选项,就能实现不同的效果,不就是通过参数来控制的嘛!带上不同的选项本质就是传入不同的参数!
到后面我们会自己来写一个命令行解释器的程序,那时候你就更能理解了!
大家都知道 main
函数其实也是有参数的,并且 C
语言的入口函数其实也不是 main
函数,而是一个叫做 startup
的函数。这就意味着,main
函数也是被其他函数调用的,那么就能够做到为 main
函数传参!
argc && argv
我们先来看看 main
函数的其中两个参数吧:
c
#include<stdio.h>
int main(int argc, char* argv[])
{
return 0;
}
- argc:整形,表示
argv
数组中的元素个数 。 - argv:一个
char*
类型的数组。
好的,不管这么多,我们直接打印这个数组来看看是个什么东西:
c
#include<stdio.h>
int main(int argc, char* argv[])
{
for(int i = 0; i < argc; i++)
{
printf("argv[%d]: %s\n", i, argv[i]);
}
return 0;
}
可以看到,argv
中的内容与运行可执行程序时是否带选项,带的选项数量都有关联:
你已经看出来了是吧:就是将运行可执行程序的方式和各种参数当做一个字符串,然后对字符串做分割,将运行可执行程序的方式和各种选项分别当做一个字符串填充到 argv
这个字符串数组中!
这样做有什么用呢 ?
我们就可以根据可执行程序加上不同的选项,表现出不同的功能啦:
我们根据 argv
的内容和数量做出判断,不同的选项对应不同的功能!
c
#include<stdio.h>
int main(int argc, char* argv[])
{
if(argc != 2 || (strcmp(argv[1], "--help") == 0))
{
printf("%s [-a|-l|-b]\n", argv[0]);
}
else
{
if(strcmp(argv[1], "-a") == 0)
{
printf("这是功能一\n");
}
else if(strcmp(argv[1], "-l") == 0)
{
printf("这是功能二\n");
}
else if(strcmp(argv[1], "-b") == 0)
{
printf("这是功能三\n");
}
else
{
printf("%s [-a|-l|-b]\n", argv[0]);
}
}
return 0;
}
这是不是就跟 ls
命令有点相似啦!
命令行参数表
命令行参数表就是那个 argv
数组哈,我们给可执行程序带的选项都会被写入命令行参数表!命令函数参数表的结构是这个样子的:
这是什么意思呢?就是说这个命令行参数表最后一个有效元素的下一个元素一定是 NULL
这个 NULL
是自动设置的!
那么我们就多了一种打印命令行参数的方式啦:
c
#include<stdio.h>
int main(int argc, char* argv[])
{
int i = 0;
for(; argv[i];i++)
{
printf("argv[%d]: %s\n", i, argv[i]);
}
return 0;
}
环境变量表获取环境变量
main
函数中可不止 argc
argv
这两个参数,还有一个参数是:char* env
。
这个 env
变量和 argv
变量是一样的!指的是结构是一样的,最后一个有效元素的下一个元素会被自动设置为 NULL
。我们也可以直接打印这个环境变量表来看看,究竟是怎么个事儿!
我们可以看到也是顺利的将环境变量给打出来了!
你可能就会有一个疑问了:
./test
会启动一个进程,在这个进程没有启动之前是没有环境变量的,但是启动这个进程之后为什么就会有环境变量呢?
- 我们运行的进程,其实都是
bash
的子进程,bash
本身在启动的时候就会从操作系统的配置文件中加载环境变量!环境变量也是数据,子进程当然会继承父进程的数据啦!换句话说,我们定义的环境变量在所有进程中是都可以被看到的,因此环境变量具有全局属性!
如何验证环境变量是可以被继承的?
首先要验证这个问题,我们需要为我们的 bash
进程添加一个自定义的环境变量!然后在子进程中获取所有的环境变量,看是否会出现我们自定义的环境变量!
那么怎么为 bash
进程添加环境变量嘞?
我们能在命令行直接定义变量嘛?向下面这样:
我们看到,这样定义变量用 echo
命令也是可以打出来的!但是我们在 env
中并没有找到我们定义的这个变量!说明这种方式定义的变量并不是环境变量!直接在命令行定义的变量叫做本地变量。这个问题我们等会再说!
想要为 bash
添加环境变量需要加上一个 export
,像下面这个样子:
想要取消一个环境变量可以使用 unset
命令,添加一个环境变量之后,unset
之后就不能查找到这个环境变量啦!
那我们就可以写代码来验证环境变量是可以被继承的啦:
c
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<stdlib.h>
int main(int argc, char* argv[], char* env[])
{
pid_t id = forrk();
if(id == 0)
{
printf("I am child process, i have env: MY_VALUE\n", getenv("MY_VALUE"));
}
else
{
sleep(100);
}
return 0;
}
可以看到子进程能够继承 bash
的环境变量!
这里有一个问题,如果在子进程中修改了从 bash
进程继承下来的环境变量,会怎么样呢?
- 环境变量也是数据,如果子进程修改父子进程共享的数据,那么就会发生写时拷贝,这个知识点在进程的创建一文中详细讲解过哦!
你可以在子进程中调用 setenv
函数,修改或者添加环境变量来验证子进程修改环境变量不会影响父进程的环境变量!
本地变量
刚才讲到过本地变量的概念:在命令行直接定义的变量。
这个本地变量在本 bash
内部有效,不会被子进程继承(验证方法跟验证环境变量能够被子进程继承是一样的,这里就不在演示了!),这是因为本地变量没有必要继承给子进程!
查看本地变量:set
,这个命令可以用来查看本地变量和环境变量!
我们定义了一个本地变量:MY_VALUE
在环境边两个中找不到这个变量,但是在 set
中能够找到!
另外本地变量也是可以变成环境变量的,你应该都猜到怎么做了~只需要 export
本地变量就能够将本地变量变成环境变量!
可以看到,将刚才定义的本地变量 MY_VALUE
export
之后就能在环境变量中查找到啦!
environ 获取环境变量
在 C 语言中,提供了一个 char**
类型的变量 environ
指向父进程的环境变量表!在使用的时候需要用 extern
声明一下,C 语言的语法嘛!
内建命令与普通命令
看上面的现象,我们定义了一个本地变量,通过 echo
能将本地变量打印出来!
我就有一个问题啦:echo
是一个命令吧,我们之前说过所有的命令都是 bash
的子进程,我们又知道,MY_VALUE
是一个本地变量,不能被子进程继承,那么,echo
命令是如何拿到 MY_VALUE
变量,并把他打印出来的呢?
- 如果你看过我之前写的文章,你就知道啦,其实并不是所有的命令都是
bash
的子进程,就比如这个echo
命令,他并不是bash
的子进程,而是bash
这个进程自己来执行的!因此echo
命令既能够打印出来环境变量也能够打印出来本地变量!
我们将那些不用创建子进程,而由 bash
进程来执行的命令称为内建命令。
我们将那些需要创建子进程,有子进程来执行的命令称为普通命令!
我们之前使用的 cd
命令也是一个内建命令,可以使用系统调用 chdir
来实现 cd
命令!
知识点:
- 环境变量的概念。
- 获取与修改环境变量
- 命令行
- 函数
- 环境变量全局属性的理解。
- 本地变量与环境变量。
- 内建命令与普通命令。