前言
相信在 it 行业学习或者工作的小伙伴们,基本都配置过环境变量(windows环境下),如果你也不知道你配置的环境变量到底是什么,或者说,你只会配置环境变量,但并不知道它到底是什么,不妨看看这篇文章,或许可以给你带来新的认识。
这篇文章会先介绍配置环境变量的本质是在干什么,然后 通过环境变量的全局属性,引出 命令行参数 ,进而理解环境变量具有全局属性,还有简单拓展本地变量与内建命令的区别。
1. 指令背后的本质
小篇之前讲过一篇文章 如何正确看待Linux下的各种命令,当时我们就谈论过了所谓 linux 下的各种指令,其本质都是一个可执行程序,而这些可执行程序(指令)与我们平常编译 C/C++ 之后形成的可执行程序有什么区别呢?
其实二者没有什么本质区别,要说区别,其一就是你的没有人家的厉害,其二就是你的不能够直接运行,而需要指定路径(./test)
。所以如果你想要,你完全可以让你自己写的程序,变的跟指令一样的执行。
没错,我们确实做到了,现在我们自己编写的程序,再也不需要通过 ./
才能运行了。
2. 环境变量背后的本质
echo $PATH // 打印系统中的PATH环境变量
ehco $HOME // 当前用户的工作目录
大家有没有想过一个问题,当我们 xshell 连接上远端服务器后,为什么会自动进入到当前用户的家目录下。凡是都有因果,而这件事,就是 xshell 中的 bash 进程为我们做的,当身份信息验证成功,bash 就会自动根据当前用户执行 cd $HOME
这样的指令。
因此,不同用户所查看到的 HOME 环境变量也是不一样的。
让我们自己编写的程序 像 指令 一样的运行,不仅可以通过将可执行程序拷贝到 /usr/bin 目录下,也可以将可执行程序所处目录路径添加到系统中的 PATH 变量中,这样同样也可以让我们的程序 像 指令 一样的执行!
PATH=$PATH:/home/outlier/linux/env
在 PATH 追加路径,一定要加上 $PATH:
否则就是直接覆盖原本的 PATH
所以环境变量背后的本质是什么?----- 我们可以简单理解为,当我们将某一个路径配置到 PATH 中,那么就可以直接像指令一样的执行 该路径下的所有可执行程序!因为系统会自动在 PATH 从左往右依次寻找这些路径下,是否有这个可执行程序,如果有,那么在执行时,系统会自动补充该程序所在路径,其道理与我们手动指定路径并且执行是一样!
但关于环境变量到底是什么,这些现象还无法说明。
3. 环境变量到底是什么
echo env
: 可以查看系统中所有的环境变量
[root@localhost ~]# env
XDG_SESSION_ID=3
HOSTNAME=localhost.localdomain
SHELL=/bin/bash
TERM=xterm
HISTSIZE=1000
USER=root
LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=01;05;37;41:su=37;41:
sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;3
1:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31
:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01
;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.jpg=01;35:*.jp
eg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.pn
g=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;
35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=0
1;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.axv
=01;35:*.anx=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=01;36:*.au=01;36:*.flac=01;36:*.mid=01;36:*.midi=01;36:*.mka=01;36:*.mp3=01;36:*.mpc
=01;36:*.ogg=01;36:*.ra=01;36:*.wav=01;36:*.axa=01;36:*.oga=01;36:*.spx=01;36:*.xspf=01;36:
MAIL=/var/spool/mail/root
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/node-v14.18.2/bin:/root/bin
PWD=/root
LANG=en_US.UTF-8
KDEDIRS=/usr
HISTCONTROL=ignoredups
SHLVL=1
HOME=/root
LOGNAME=root
XDG_DATA_DIRS=/root/.local/share/flatpak/exports/share:/var/lib/flatpak/exports/share:/usr/local/share:/usr/share
LESSOPEN=||/usr/bin/lesspipe.sh %s
DISPLAY=localhost:11.0
NODE_HOME=/usr/local/node-v14.18.2
XAUTHORITY=/root/.xauthYF8InL
_=/bin/env
OLDPATH=/home/outlier/linux
以上就是我自己系统中所有的环境变量了,其中较为重要的有如下
- HISTSIZE 就是用来保存历史指令的,HISTSIZE=1000 就决定了只会保存最近的1000条指令
- USER=root 则记录当前登录的用户名
- PWD=/root 记录当前用户所处的路径
- LOGNAME=root 记录当前登录的用户
- OLDPATH=/home/outlier/linux 记录上一次所处路径,这也是为什么执行
cd -
的时候,总是可以直接返回上一次路径,经过 bash 的解释之后,最终其实就是在执行cd $OLDPATH
这条指令。
刚刚我们是通过系统指令 env 或者 echo 某一个环境变量来获取的,下面我要介绍一种在代码层面获取环境变量的方法。
include<stdio.h>
#include<stdlib.h>
int main()
{
printf("who: %s\n", getenv("USER"));
printf("PATH: %s\n", getenv("PATH"));
return 0;
}
其中值得关注的是,不同用户执行该程序时,getenv() 获取的环境变量的值并不相同! 这也说明了程序内部是可以通过获取环境变量来得知当前运行该程序是哪个用户。所以当环境变量认识到这一步,我们必须要重新认识一下 linux 中的权限问题。
Linux权限的理解与操作 这篇文章过后,我们应该要知道,权限分两种,一种对人,一种对物。而今天我们要重新认识对人的权限,凭什么一个文件属性上写着拥有者是谁,所属组是谁,就能决定这个人有没有权限。凭的就是当程序被用户运行时,它内部是可以通过 getenv 去获取当前是哪个用户在访问的,换言之,程序本身就可以决定允许被谁读写执行等操作。 为了让这方面的理解更加直观,我们还可以再来一个 demo。
int main()
{
char who[32] = {0};
strcpy(who, getenv("USER"));
if(strcmp(who, "root") == 0)
{
printf("你是上帝,所以你随便玩!\n");
}
else
{
printf("你是普通用户,要受到权限的约束!\n");
}
return 0;
}
所以环境变量到底是什么?? ---- 环境变量是系统提供的一组 name=value 形式的变量,不同的环境变量有不同的用户,通常具有全局属性。
那为什么是环境变量具有全局属性呢?
4. 命令行参数
在回答上面的问题之前,我们需要先讲一下 命令行参数。
简单来说,命令行参数就是我们各种命令后面带的选项,例如 ls -l
,ls -a
,ls -a -l -i
等等,ls 后面带的这些选项都叫做命令行参数!
而 C/C++ 程序当中的 main 函数,其实也是有参数传递的,只不过在 windows 平台下基本不用到而已。
int main(int argc, char* argv[], char* env[])
{
int i = 0;
for(; argv[i]; ++i)
{
printf("argv[%d]->%s\n", i, argv[i]);
}
}
// 其中的 argv 和 env 就是一个指针数组,而 char* 一般所指的就是字符串的起始地址
// 换言之,在命令行的一切指令,最终都会被 bash 解释成字符串,存入到 argv 数组中
// env 则是存储环境变量的数组
上述 demo 就很好的验证了,argv 这个数组就是在存储我们的指令 以及附带的各个选项
接着,为了更好理解 命令行参数 跟我们所谓的 指令选项 有什么关联,我们可以来一个 demo
int main(int argc, char* argv[], char* env[])
{
if(argc != 2)
{
printf("Usage: %s -[a|b|c|d]\n", argv[0]);
return 0;
}
if(strcmp(argv[1], "--help")==0)
{
printf("Usage: %s -[a|b|c|d]\n", argv[0]);
}
else 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 if(strcmp(argv[1], "-d") == 0)
{
printf("功能4\n");
}
else
{
printf("default功能\n");
}
}
所以现在应该能够理解所谓 命令行参数 是在干什么了吧 或者 为什么要设计 命令行参数 ? ----- 为指令、工具,软件等提供命令行选项的支持!,这样在用户层看到的结果一直都是,带上不同的选项,我就可以看到不同的运行效果;站在开发者的角度,我就可以通过设置各种 命令行参数 来实现不同选项不同效果的体现!
但是现在有一个问题,main 函数当中的参数是谁给传递进来的呢??
我们要明白一个因果,既然有参数,那就势必会有传参这个行为。虽然我们一直在说 main 函数是整个代码程序的入口,但我们从来没有说过它不是一个函数,既然是函数,那就会存在两种行为,一种是调用别的函数,一种是被别的函数调用。所以既然 main 函数有参数,那就说明它一定会被别的函数调用!在我们编译代码的时候,编译器内部就会有一个 Startup() 这个的函数调用我们的 main(),然后对 main 函数做一个传参检查,最后编译我们的代码。
而 main 有了传参,并且其中一个是用于存储环境变量的数组,那么现在,我们也可以不通过 getenv 来获取环境变量了,可以直接遍历 env[ ] 来获取所有的环境变量(getenv() 只能获取指定的一个环境变量)。
int main(int argc, char* argv[], char* env[])
{
int i = 0;
for(; env[i]; ++i)
{
printf("env[%d]->%s\n", i, env[i]);
}
}
并且,通过代码层面上对 env 数组遍历出来的结果,与 bash 中的 env 指令中获取的环境变量,是一模一样的!而这恰恰可以说明 环境变量具有全局属性 这件事!
我们所有的程序,在运行起来后都会转变为进程,而这些进程都是 bash 的子进程。而 bash 本身在启动的时候,会从操作系统的配置文件中读取环境变量信息,子进程会继承父进程(bash)的环境变量!而作为 bash 的子进程,在不需要修改环境变量的数据时,子进程关于环境变量的数据 与 父进程(bash)是一模一样的!
换言之!所有的子进程都会继承 bash 的环境变量,而子进程的子进程又会继承其父进程的环境变量,这样下来,所有的进程都能够获取到环境变量,这不就是 环境变量具有全局属性吗!!
5. 本地变量 与 内置命令
张三:我承认小篇你讲的很好,但是上面这些我还是不相信,环境变量具有全局属性这件事。
所以我们再谈一个话题,那就是什么是本地变量?什么是内置命令?
在本 bash 进程内定义的变量,就称为本地变量,并且不会被继承!---- 如何证明它不会被继承?
int main(int argc, char* argv[], char* env[])
{
printf("MYVALUE: %s\n", getenv("MYVALUE"));
}
我们在 bash 中定义的本地变量,我们是可以查看到,但是我们自己编写的程序被运行起来,就会变为 bash 的子进程,而 bash 可以查到本地变量,其子进程却不行,这不就证明了本地变量不具备全局属性,不会被子进程继承!反之,当我们 export 设置为环境变量之后,其子进程是会继承父进程的环境变量的,因此我们运行程序之后,又发现突然查得到了,再次证明环境变量具有全局属性这件事!
不过,这里有一处疑点。当我们 ehco $MYVALUE
是可以查到本地变量的。之前我们 ps axj 查看进程并且用 grep 命令过滤关键词的时候,出来的结果中还有 grep 这个进程,当时我们就说,grep 在执行的时候,bash 同样也会为其创建一个子进程。那这么说,执行 echo 的时候,同样也会创建子进程在执行啊,你刚刚才说的 子进程不会继承本地变量,所以获取不到本地变量,怎么今天 执行 ehco 创建出来的子进程,好像继承了本地变量?(因为 echo 能够打印这个本地变量)
echo 就是我们要说的内建命令!
并不是所有的命令都会被创建子进程,有些命令是不需要 bash 创建子进程的,而是由 bash 亲自执行,这类命令大都是 bash 内部自己写的 或者 系统提供的函数,我们将这类命令称为 内建命令。
而 cd 命令就是典型的内建命令。怎么证明呢? ----- 当我们在 cd xxx 进入某个路径时,如果你说 bash 会为 cd 命令创建子进程,然后让子进程去执行这条命令,那我们改的应该也是子进程的路径,但为什么 bash 中的路径也被更改了呢?我们 cd 的时候,bash 的命令行一直是实时更新我们当前所处的目录的!所以这就说不通了,只能说明 cd 是内建命令,由 bash 亲自执行,并没有创建子进程去执行!所以当我们 cd xxx,改的当然就是 bash 的路径了。
我们在通过一个 demo 来感受 cd 命令
int main(int argc, char* argv[], char* env[])
{
// bash 在执行 cd 命令的时候,类似如下,判断到是 cd 命令后,不创建子进程,直接通过 chdir 修改路径!
if(strcmp(argv[1], "cd")==0)
{
chdir(argv[1]);
}
// 模拟 cd 命令调用 chdir
sleep(30);
printf("path begin changing!\n");
if(argc == 2)
{
chdir(argv[1]);
}
printf("path change end!\n the process is sleeping!\n");
sleep(30);
}
cd 命令就是类似如此,执行的时候直接调用 chdir 修改路径,并不会 frok() 创建子进程。
6. 环境变量的相关命令
echo: 显示某个环境变量值
export: 设置一个新的环境变量
env: 显示所有环境变量
unset: 清除环境变量
set: 显示本地定义的shell变量和环境变量
关于环境变量的内容,到这里就结束了。
如果感觉该篇文章给你带来了收获,可以 点赞👍 + 收藏⭐️ + 关注➕ 支持一下!
感谢各位观看!