欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

环境变量与 shell 的那些事

程序员文章站 2022-05-07 08:20:19
...

0. 前言及废话

你是否每次配置环境变量都要百度一番?你是否不解为何 export 后环境变量就会直接生效而退出再登录就没用了?你是否不明白为何已经改了配置文件还要 source 命令一下,source 命令的原理又是什么?如果答案是 Yes,则此文非常适合你!

本文讲述了 shell 的环境变量的内存存储位置、shell 内置命令的原理,以及其配置文件初始化顺序,对于不知道环境变量如何配置和其配置原理的童鞋们有入门引导的作用。相信阅读过后,再配置环境变量时,便能在心中清楚知道为何在此配置。

1. 环境变量

环境变量中的所谓环境是指操作系统中的进程所处的环境,以 Key-Value 的形式保存在进程的一块高地址空间中。系统中的很多进程依赖于环境变量提供的信息,待需要时便使用系统提供的函数获取。例如获取用户名、HOME 目录等等。

从父进程创建的子进程继承了父进程环境变量,子进程从父进程直接拷贝一份环境变量表,所以子进程对其环境变量的更改只是在其副本之上操作,不会影响父进程。
环境变量与 shell 的那些事
进程对环境变量的操作有可能使环境变量表的存储位置发生移动。当删除一个环境变量或者缩减一个环境变量值的长度时,环境变量所占空间更少,不会影响环境变量表。但当增加环境变量或者加长一个环境变量的值长度时,需要更多的空间,则会对环境变量表产生影响。

环境变量表在进程的高地址处的一个固定空间中,下方便是栈底,所以不可能扩充这部分的空间。《UNIX环境高级编程》指出,当发生需要扩充空间的时候,会向堆申请空间,将原来的环境变量表复制到新的空间中,再对表进行改变,最后重置 env 环境表指针。

2. shell 的环境变量初始化过程

shell 进程便使用了大量环境变量,例如,当我们在 shell 中输入 cd ~ 时,shell 便查找环境变量 HOME,得知用户的目录,再 chdir 过去。

当我们键入 ps 时,shell 将 ps 的镜像载入内存并将控制权转移,运行 ps。而其第一步便是找到 ps 在何处,然而并未给出绝对路径,shell 便通过环境变量来查找这个命令镜像的位置。shell 使用的这个环境变量是 PATH,在 shell 中键入 echo $PATH 便可以查看到这个环境变量值,是一个以冒号分割的字符串,每个子串是一个绝对路径。shell 在这些目录中依次查找,当有匹配时便使用这个位置的相应命令。
通过如下的程序输出了当前进程的环境变量:

extern char **environ;
...
for (i = 0; environ[i] != NULL; ++i)
    printf("%d: %s\n", i, environ[i]);

以上程序输出了一大堆的环境变量:SHELLUSERPATHHOMEJAVA_HOME 等等,这些环境变量和 shell 的环境变量是一致的,因为这个进程是由 shell 拉起来的。而 shell 则通过一些配置文件来添加环境变量。

linux配置java环境变量(详细) 这篇为例,其中写到在 profile 文件的末尾添加如下的文本:

export JAVA_HOME=/usr/share/jdk1.6.0_14 
export PATH=$JAVA_HOME/bin:$PATH 
export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar 

在 shell 中,export 是一个内置命令,用于设置或显示环境变量。如果在命令行中 export 一个变量,在退出之后再重新登录这个环境变量便不复存在,其原因在于 export 这个内置命令使用 setenv 或者 putenv 将后面的表达式插入到 shell 进程的环境变量表中,当 shell 进程结束时,这些变量也就销毁掉了。所以为了在登录 shell 后便可以使用很多的环境变量,使用一组配置文件保存想要保存的环境变量。实际上这些“配置文件”是一条条的 shell 命令,shell 在每次启动的时候依次执行这些命令,如果命令中有 export,便将环境变量添加到进程中。

所以上面的文章中提到了在修改 /etc/profile 文件后重新登录,才会生效,就是在重新登录时会再次运行这些命令,将新的环境变量添加进来。而 shell 还有另一个内置命令 source,将其读入的脚本里面的命令依次在当前 shell 中执行而不新建子 shell 执行,脚本中所执行的命令可以改变当前 shell 的状态。而如果直接运行这个脚本,则会启动新的 shell,所做的改变对当前 shell 无影响。故可以 source /etc/profile 来重新执行改变后的 /etc/profile 来更新环境变量。

具体来说,在我的 CentOS 上使用的 shell 是 bash,在登录时,bash 会先读 /etc/profile 中的命令。此文件是所有用户登录 bash 时都会读取的文件,用于所有用户的公共配置。代码如下:

if [ -f /etc/bash.bashrc ]; then
    . /etc/bash.bashrc
fi
...
if [ -d /etc/profile.d ]; then
    for i in /etc/profile.d/*.sh; do
        if [ -r $i ]; then
            . $i
        fi
    done
    unset i
fi

首先判断是否存在 /etc/bash.bashrc 文件,很遗憾在我的机器上并未找到这个文件;其次在 /etc/profile.d 目录下依次执行里面的 .sh 脚本,进行配置。

在读取了全局配置之后,会读取用户自己的 ~/.bash_profile:

if [ -f ~/.bashrc ]; then
    . ~/.bashrc
fi

在此处执行了 ~/.bashrc 中的命令,为何不在此处直接写上所有的环境变量定义,而偏偏要在 ~/.bashrc 中简介调用?上述提到的这两个 “profile” 均是在登录时才会执行的,而不需要登录的 shell 则不执行。两者之间有大量重叠,故将重叠的命令放在 ~/.bashrc 中,无论是否是登录执行的 shell 都可以用到。

if [ -f /etc/bashrc ]; then
    . /etc/bashrc
fi

在此文件中则使用了 /etc/bashrc 中的指令… 经过这样的文件读取流程后,一个 shell 也初始化好了其环境变量。实质上就是将文本中所定义的环境变量 KY 对最终放入内存中的环境变量表,进程负责利用并解释所用到的变量。
执行顺序总结如下图所示
环境变量与 shell 的那些事