Linux系统-环境变量
【Linux系统】—— 环境变量
1 概念介绍
- 环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数
- 如:我们在编写 C/C++ 代码的时候,在链接的时候,从来不知道我们所链接的动静态库在哪里,但是照样可以链接成功,生成可执行程序,原因就是有相关环境变量帮助编译器进行查找
- 环境变量通常具有某些特殊用途,还有在系统当中通常具有全局特性
2 命令行参数
2.1 main 函数有参数吗?
问大家一个问题:main 函数有参数吗?
int main()
{
return 0;
}
好像我们平时写的 main函数 都是不用传参的,但不传参就代表不用参数吗?
main函数 至少有两个参数:int argc
和 char *argv[]
int main(int argc, char *argv[])
{
return 0;
}
main函数 仅仅是我们自己的代码的入口,实际上 main函数 也要被其他函数(Linux中为 _start
函数)调用。
2.2 argc 与 argv
现在我们知道 main函数 有两个参数 argc
与 argv
,他们是什么呢?有什么用呢?
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数组 中,数组中有多少个参数由 argc 指明。同时有效元素放完必须以NULL结尾。
2.3 命令如何通过不同参数实现不同子功能
看到这里,不知道大家有没有想到什么?
我们平时用的命令,他们本质上就是可执行程序,且一般是由 C语言 写的。我们可以在后面带命令行选项:ls -a -l
中的 -a
和 -l
。我们输入不同的选项,命令可以执行不同的子功能。如果做到的呢?不急,我们先写一段代码
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{
if(argc != 2)
{
printf("Usage:%s [-a|-b|-c]\n", argv[0]);
return 1;
}
const char *arg = argv[1];
if(strcmp(arg, "-a") == 0)
printf("这是功能1\n");
else if(strcmp(arg, "-b") == 0)
printf("这是功能2\n");
else if(strcmp(arg, "-c") == 0)
printf("这是功能3\n");
else
printf("Usage:%s [-a|-b|-c]\n", argv[0]);
return 0;
}
代码逻辑:如果未带选项或者选项错误,打印提示信息;否则执行对应子功能
所以:命令行参数是为了让一个程序可以通过选项实现不同的子功能。这也是指令选项的实现原理!
注:./code
也算一个命令行参数,所以判断语句是 if(argc != 2) 而不是 if(argc != 1)
谁帮我们进行切分的呢?是shell
,Linux 中即 bash
。
为什么我们之前不用带命令行参数呢?因为不需要。我们之前写的代码功能就一个,如果我们想让我的代码有不同的子功能就可以带命令行参数
所以在进程启动时,有一张表 —— argv表,用来支持实现选项功能。
3 环境变量:PATH
我们发现,执行我们指令的程序需要带 「./」
,而执行系统的命令就不需要带 「./」
。这是为什么呢?
无论是我们自己写的程序还是系统的指令,没有本质区别,都是二进制程序。那为什么一个要带路径一个不用呢?
虽然不知道为什么,但有一点我们清楚:要执行一个程序,必须先找到他!
正因为要找到它,所以我们运行自己的程序要带「./」表明是在当前路径。我们执行系统命令就不用呢?原因是系统中存在环境变量 PATH
,来帮助系统找到目标二进制文件。
环境变量 PATH 默认情况下在系统中存在,用来标识一串路径,以用来告诉系统去那些路径下找二进制文件。
3.1 查看环境变量
怎么查看PATH环境变量呢?
在系统中,查看所有环境变量
使用指令:env
环境变量也是一个变量,环境变量的构成:名称=内容
只想看指定环境变量的内容:
使用指令:echo $环境变量名称
我们发现 PATH 中的内容都是以绝对路径的形式呈现,以 :
作为分隔符
当我们输入 ls 等指令,系统不是直接去 /usr/bin 路径去查;而是依次遍历所有路径,最终在 /usr/bin 路径下找到 ls 指令。如果将所有路径遍历完依然没找到,就会报 Command not found。
3.2 将路径添加进 PATH 中
我们将自己当前的路径添加到 PATH 中,那我们写的 code 可执行也可以不带「./」直接就能被系统找到。
如何将路径添加进 PATH 中呢?
PATH 已经是一个变量,我们直接对其赋值
这样我们的指令就能不带 「./」 直接被系统找到了
但是,系统的指令找不到了
原因是刚刚我们 PATH=/home/gy/lesson13
是覆盖式的写,将 PATH 中原来的路径覆盖了
怎么改回来?不用担心,PATH是一个内存级的变量,想恢复只需重新登录账号即可。
如何追加式的将路径写入PATH呢?如下图:
当然,如果再退出后登录,PATH 依然恢复默认值
4 环境变量从哪里来
4.1 从存储的角度理解环境变量
环境变量是由名称和内容组成的,那环境变量是由谁来保存的呢?答案是 bash。
当我们一旦登录时,系统就会给我们创建一个 bash 进程。bash 从系统中读取所有的环境变量信息,然后在 bash 内部形成一张表 —— 环境变量表。环境变量表和上面的 argv 一样都是指针数组。上述所查看的所有环境变量名称=内容
都是一个个字符串,环境变量表指向这一个个字符串
我们输入的指令如:ls -a -b
首先是被 bash 拿到,bash将 ls -a -b
拆分成 ls
-a
-b
,构建一张命令行参数表。后拿着 ls 这个命令,在环境变量表中找到 PATH,再在 PATH 中的各个路径找 ls。所以指令的查找工作是由 bash 自己完成的
结论:bash 内部会有两张表:命令行参数表和环境变量表
4.2 环境变量最开始从哪来
我不登录不就没有 bash 了吗?那环境变量最开始从哪来呢?
环境变量是从系统相关的配置文件来。
bash 最开始启动时,会从配置文件中读取多有环境变量的信息,并在自己内部创建环境变量表
这个配置文件怎么看?
vim .bash_profile
vim .bashrc
vim /etc/bashrc
我们登录时,bash 就会被创建。bash 就会读取 .bashrc
和 .bash_profile
配置文件的内容构建出环境变量表。
如果我们在配置文件中加入自己的路径,那么以后登录,我们的 code 就能不带路径自动被执行。
如果 Linux 中有 10 个用户登录,系统中就存在 10 个 bash。是不是意味着这 10 个 bash 每一个都要从配置文件中奖将环境变量读到自己的 bash 上下文中?所以每一个 bash 中都存在两张表:命令行参数表和环境变量表。
5 认识更多环境变量
每一个环境变量都有其特定的功能,下面我们介绍几个
HOME
:当前用户的家目录
SHELL
:当前用户登录时,用的是哪一个版本的 shell。默认保存的是 bash 的路径(/bin/bash)
USER
:当前用户
HISTSIZE
:记录最大历史命令个数
PWD
:当前的工作路径
OLDPWD
:记录上一次所处的工作路径
所以为什么 cd - 可以做最近两次路径的切换?环境变量给我们提供好了
6 对环境变量的操作
前文,我们已经学会了两个获取环境变量的方法
env
echo $xxx
6.1 导入环境变量
导入环境变量:
使用指令:export 要导入变量名=要导入变量内容
export MYENV=11223344
不知大家有没有觉得奇怪
环境变量是在谁的上下文中的?是 bash。而 export 是命令,是一个由 bash 创建的子进程。进程之间是互不影响的。所以为什么在进程 export 中的操作会影响进程 bash 呢?会改变父进程的环境变量表呢?
只有一个解释:export 命令是一个内建命令。
内建命令:不需要创建子进程,由 bash 自己亲自执行
。一般是由 bash 调用函数或者系统调用完成
6.2 删除环境变量
删除环境变量:
使用指令:unset 环境变量名
unset MYENV
6.3 通过代码的方式获得环境变量
6.3.1 main函数参数获取环境变量
main函数有参数吗?有!
main函数最多有几个参数?
3个!
那还有一个参数是谁呢?
最后一个是 char *env[]
int main(int argc, char *argv[], char *env[])
{
return 0;
}
char *env[]
是什么呢?
char *env[]
是一个指针数组,未来父进程 (如:bash) 可以把环境变量表传给 main 函数,由 char *env[] 接收。环境变量表与命令行参数表的形式是一模一样的,最后都以NULL结尾
我们写一段代码验证一下
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[], char *env[])
{
(void)argc;
(void)argv;
int i = 0;
for(; env[i]; ++i)
{
printf("env[%d]->%s\n", i, env[i]);
}
return 0;
}
注:(void)argc;
的写法是为了不让编译器报警告。在 gcc 中如果我们定义了变量确不去用他会报警告,将其强转表示使用了 argc 变量,就不会报警告
此时 main函数 就获取了环境变量,这个环境变量是谁的呢?是父进程也就是 bash 的。
结论:环境变量可以被子进程继承。环境变量在系统中具有全局特性
注:Linux 中 main 函数是由 _start
函数调用的,_start 会先扫描 main 函数的参数个数,再执行相应 main函数。大致如下:
_start
{
int ret = 0;
int arg_count = 0;
//扫描 main 函数的参数个数
arg_count = 0/2/3;
if(arg_count == 0)
ret = main();
else if(arg_count == 2)
ret = main(argc, argv);
else
ret = main(argc, argv, env);
}
6.3.2 getenv 函数获取环境变量
输入指令:man 3 getenv
查看 getenv
函数
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;
printf("PATH->%s\n", value);
return 0;
}
如果我只想让我写的程序只能我执行,就算 root 也没门,该怎么设计?
我们可以运用环境变量。
整个系统只有一个人知道登录用户是谁:bash
。在 bash 中的环境变量表中记录着环境变量 USER,而 bash 可以将环境变量交给子进程,子进程可以根据环境变量进行身份识别
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, char *argv[], char *env[])
{
(void)argc;
(void)argv;
(void)env;
char *who = getenv("USER");
if(who == NULL)
return 1;
if(strcmp(who, "gy") == 0)
printf("这是程序正常执行逻辑\n");
else
printf("只有 gy 能执行\n");
return 0;
}
为什么全局变量要有全局特性呢?
因为这样子进程就可以将环境变量继承下去,子进程就可以结合环境变量做个性化操作。比如上面指定一个只能直接执行的程序。
6.3.3 全局变量 environ 获取环境变量
输入指令:man environ
全局变量 environ
environ 指向环境变量表
为什么 environ 是 char** 类型的,因为environ指向的是环境变量表,而环境变量表本身就是一个指针数组
注:使用 environ 之前,需将其声明
#include <stdio.h>
#include <unistd.h>
extern char **environ;
int main(int agrc, char *agrv[])
{
(void)agrc;
(void)agrv;
int i = 0;
for(; environ[i]; ++i)
{
printf("environ[%d]->%s\n", i, environ[i]);
}
return 0;
}
上述三种获取环境变量的方法中,我们最常用的是第二种,因为法一和法三都是获取整张环境变量表,而法二获取的是单个环境变量的内容。
7 本地变量
先看操作:
如上图,我们可以直接在命令行中定义变量,我们将这个变量称为本地变量。
此时我们用 env 查看环境变量,发现并没有找到 i 变量。这也说明我们定义的 i 变量不是环境变量
查看本地变量:
使用指令:set
set
可以查看本地变量和环境变量
删除本地变量:
使用指令:unset
本地变量名
所以什么是本地变量呢?
本地变量不会被子进程继承,只会在 bash 内部被使用。
好啦,本期关于 环境变量 的知识就介绍到这里啦,希望本期博客能对你有所帮助。同时,如果有错误的地方请多多指正,让我们在 Linux 的学习路上一起进步!