环境变量在Linux系统中的用途很多。你现在已经知道如何修改系统环境变量,也知道了如
何创建自己的环境变量。接下来的问题是怎样让环境变量的作用持久化。
在你登入Linux系统启动一个bash shell时,默认情况下bash会在几个文件中查找命令。这些
文件叫作启动文件或环境文件。bash检查的启动文件取决于你启动bash shell的方式。启动bash
shell有3种方式:

  • 登录时作为默认登录shell
  • 作为非登录shell的交互式shell
  • 作为运行脚本的非交互shell

下面几节介绍了bash shell在不同的方式下启动文件。

6.6.1 登录 shell

当你登录Linux系统时,bash shell会作为登录shell启动。登录shell会从5个不同的启动文件里
读取命令:

  • /etc/profile
  • $HOME/.bash_profile
  • $HOME/.bashrc
  • $HOME/.bash_login
  • $HOME/.profile

/etc/profile文件是系统上默认的bash shell的主启动文件。系统上的每个用户登录时都会执行
这个启动文件。


说明 要留意的是有些Linux发行版使用了可拆卸式认证模块(Pluggable Authentication
Modules ,PAM)。在这种情况下,PAM文件会在bash shell启动之前处理,这些文件中可
能会包含环境变量。PAM文件包括/etc/environment文件和$HOME/.pam_environment文件。
PAM更多的相关信息可以在http://linux-pam.org中找到。


另外4个启动文件是针对用户的,可根据个人需求定制。我们来仔细看一下各个文件。

1. /etc/profile文件

/etc/profile文件是bash shell默认的的主启动文件。只要你登录了Linux系统,bash就会执行
/etc/profile启动文件中的命令。不同的Linux发行版在这个文件里放了不同的命令。在本书所用的
Ubuntu Linux系统上,它看起来是这样的:

# /etc/profile: system-wide .profile file for the Bourne shell (sh(1)) 
# and Bourne compatible shells (bash(1), ksh(1), ash(1), ...). 
if [ "$PS1" ]; then 
 if [ "$BASH" ] && [ "$BASH" != "/bin/sh" ]; then 
 # The file bash.bashrc already sets the default PS1. 
 # PS1='\h:\w\$ ' 
 if [ -f /etc/bash.bashrc ]; then 
 . /etc/bash.bashrc 
 fi 
 else 
 if [ "`id -u`" -eq 0 ]; then 
 PS1='# ' 
 else 
  PS1='$ ' 
 fi 
 fi 
fi 
# The default umask is now handled by pam_umask. 
# See pam_umask(8) and /etc/login.defs. 
if [ -d /etc/profile.d ]; then 
 for i in /etc/profile.d/*.sh; do 
 if [ -r $i ]; then 
 . $i 
 fi 
 done 
 unset i 
fi

这个文件中的大部分命令和语法都会在第12章以及后续章节中具体讲到。每个发行版的
/etc/profile文件都有不同的设置和命令。例如,在上面所显示的Ubuntu发行版的/etc/profile文件中,
涉及了一个叫作/etc/bash.bashrc的文件。这个文件包含了系统环境变量。

但是,在下面显示的CentOS发行版的/etc/profile文件中,并没有出现这个文件。另外要注意
的是,该发行版的/etc/profile文件还在内部导出了一些系统环境变量。

# /etc/profile 
# System wide environment and startup programs, for login setup 
# Functions and aliases go in /etc/bashrc 
# It's NOT a good idea to change this file unless you know what you 
# are doing. It's much better to create a custom.sh shell script in 
# /etc/profile.d/ to make custom changes to your environment, to 
# prevent the need for merging in future updates. 
pathmunge () { 
 case ":${PATH}:" in 
 *:"$1":*) 
 ;; 
 *) 
 if [ "$2" = "after" ] ; then 
 PATH=$PATH:$1 
 else 
 PATH=$1:$PATH 
 fi 
 esac 
} 
if [ -x /usr/bin/id ]; then 
 if [ -z "$EUID" ]; then 
 # ksh workaround 
 EUID=`id -u` 
 UID=`id -ru` 
 fi 
 USER="`id -un`" 
 LOGNAME=$USER 
 MAIL="/var/spool/mail/$USER" 
fi 
# Path manipulation 
if [ "$EUID" = "0" ]; then 
 pathmunge /sbin 
 pathmunge /usr/sbin 
 pathmunge /usr/local/sbin 
else 
 pathmunge /usr/local/sbin after 
 pathmunge /usr/sbin after 
 pathmunge /sbin after 
fi 
HOSTNAME=`/bin/hostname 2>/dev/null` 
HISTSIZE=1000 
if [ "$HISTCONTROL" = "ignorespace" ] ; then 
 export HISTCONTROL=ignoreboth 
else 
 export HISTCONTROL=ignoredups 
fi 
export PATH USER LOGNAME MAIL HOSTNAME HISTSIZE HISTCONTROL 
# By default, we want umask to get set. This sets it for login shell 
# Current threshold for system reserved uid/gids is 200 
# You could check uidgid reservation validity in 
# /usr/share/doc/setup-*/uidgid file 
if [ $UID -gt 199 ] && [ "`id -gn`" = "`id -un`" ]; then 
 umask 002 
else 
 umask 022 
fi 
for i in /etc/profile.d/*.sh ; do 
 if [ -r "$i" ]; then 
 if [ "${-#*i}" != "$-" ]; then 
 . "$i" 
 else 
 . "$i" >/dev/null 2>&1 
 fi 
 fi 
done 
unset i 
unset -f pathmunge

这两个发行版的/etc/profile文件都用到了同一个特性:for语句。它用来迭代/etc/profile.d目录下的所有文件。(该语句会在第13章中详述。)这为Linux系统提供了一个放置特定应用程序启
动文件的地方,当用户登录时,shell会执行这些文件。在本书所用的Ubuntu Linux系统中,
/etc/profile.d目录下包含以下文件:

$ ls -l /etc/profile.d
total 12
-rw-r—r— 1 root root 40 Apr 15 06:26 appmenu-qt5.sh
-rw-r—r— 1 root root 663 Apr 7 10:10 bash_completion.sh
-rw-r—r— 1 root root 1947 Nov 22 2013 vte.sh
$
在CentOS系统中,/etc/profile.d目录下的文件更多: $ ls -l /etc/profile.d
total 80
-rw-r—r—. 1 root root 1127 Mar 5 07:17 colorls.csh
-rw-r—r—. 1 root root 1143 Mar 5 07:17 colorls.sh
-rw-r—r—. 1 root root 92 Nov 22 2013 cvs.csh
-rw-r—r—. 1 root root 78 Nov 22 2013 cvs.sh
-rw-r—r—. 1 root root 192 Feb 24 09:24 glib2.csh
-rw-r—r—. 1 root root 192 Feb 24 09:24 glib2.sh
-rw-r—r—. 1 root root 58 Nov 22 2013 gnome-ssh-askpass.csh
-rw-r—r—. 1 root root 70 Nov 22 2013 gnome-ssh-askpass.sh
-rwxr-xr-x. 1 root root 373 Sep 23 2009 kde.csh
-rwxr-xr-x. 1 root root 288 Sep 23 2009 kde.sh
-rw-r—r—. 1 root root 1741 Feb 20 05:44 lang.csh
-rw-r—r—. 1 root root 2706 Feb 20 05:44 lang.sh
-rw-r—r—. 1 root root 122 Feb 7 2007 less.csh
-rw-r—r—. 1 root root 108 Feb 7 2007 less.sh
-rw-r—r—. 1 root root 976 Sep 23 2011 qt.csh
-rw-r—r—. 1 root root 912 Sep 23 2011 qt.sh
-rw-r—r—. 1 root root 2142 Mar 13 15:37 udisks-bash-completion.sh
-rw-r—r—. 1 root root 97 Apr 5 2012 vim.csh
-rw-r—r—. 1 root root 269 Apr 5 2012 vim.sh

-rw-r—r—. 1 root root 169 May 20 2009 which2.sh

$

不难发现,有些文件与系统中的特定应用有关。大部分应用都会创建两个启动文件:一个供
bash shell使用(使用.sh扩展名),一个供c shell使用(使用.csh扩展名)。

lang.csh和lang.sh文件会尝试去判定系统上所采用的默认语言字符集,然后设置对应的LANG
环境变量。

2. $HOME目录下的启动文件

剩下的启动文件都起着同一个作用:提供一个用户专属的启动文件来定义该用户所用到的环
境变量。大多数Linux发行版只用这四个启动文件中的一到两个:

  • $HOME/.bash_profile
  • $HOME/.bashrc
  • $HOME/.bash_login
  • $HOME/.profile

注意,这四个文件都以点号开头,这说明它们是隐藏文件(不会在通常的ls命令输出列表中
出现)。它们位于用户的HOME目录下,所以每个用户都可以编辑这些文件并添加自己的环境变
量,这些环境变量会在每次启动bash shell会话时生效。


说明 Linux发行版在环境文件方面存在的差异非常大。本节中所列出的$HOME下的那些文件并
非每个用户都有。例如有些用户可能只有一个$HOME/.bash_profile文件。这很正常。


shell会按照按照下列顺序,运行第一个被找到的文件,余下的则被忽略:

$HOME/.bash_profile  
$HOME/.bash_login  
$HOME/.profile

注意,这个列表中并没有$HOME/.bashrc文件。这是因为该文件通常通过其他文件运行的。


窍门 记住,$HOME表示的是某个用户的主目录。它和波浪号(~)的作用一样。


CentOS Linux系统中的.bash_profile文件的内容如下:

cat $HOME/.bash_profile

# .bash_profile 
# Get the aliases and functions 
if [ -f ~/.bashrc ]; then 
 . ~/.bashrc 
fi 
# User specific environment and startup programs 
PATH=$PATH:$HOME/bin 
export PATH

.bash_profile启动文件会先去检查HOME目录中是不是还有一个叫.bashrc的启动文件。如果有
的话,会先执行启动文件里面的命令。

6.6.2 交互式 shell 进程

如果你的bash shell不是登录系统时启动的(比如是在命令行提示符下敲入bash时启动),那
么你启动的shell叫作交互式shell。交互式shell不会像登录shell一样运行,但它依然提供了命令行
提示符来输入命令。

如果bash是作为交互式shell启动的,它就不会访问/etc/profile文件,只会检查用户HOME目录
中的.bashrc文件。
在本书所用的CentOS Linux系统上,这个文件看起来如下:

cat .bashrc

# .bashrc 
# Source global definitions 
if [ -f /etc/bashrc ]; then 
 . /etc/bashrc 
fi 
# User specific aliases and functions

bashrc文件有两个作用:一是查看/etc目录下通用的bashrc文件,二是为用户提供一个定制自
己的命令别名(参见第5章)和私有脚本函数(将在第17章中讲到)的地方。

6.6.3 非交互式 shell

最后一种shell是非交互式shell。系统执行shell脚本时用的就是这种shell。不同的地方在于它
没有命令行提示符。但是当你在系统上运行脚本时,也许希望能够运行一些特定启动的命令。


窍门 脚本能以不同的方式执行。只有其中的某一些方式能够启动子shell。你会在第11章中学习
到shell不同的执行方式。


为了处理这种情况,bash shell提供了BASH_ENV环境变量。当shell启动一个非交互式shell进
程时,它会检查这个环境变量来查看要执行的启动文件。如果有指定的文件,shell会执行该文件
里的命令,这通常包括shell脚本变量设置。

在本书所用的CentOS Linux发行版中,这个环境变量在默认情况下并未设置。如果变量未设
置,printenv命令只会返回CLI提示符:

printenv BASH_ENV

在本书所用的Ubuntu发行版中,变量BASH_ENV也没有被设置。记住,如果变量未设置,echo
命令会显示一个空行,然后返回CLI提示符:

echo $BASH_ENV

那如果BASH_ENV变量没有设置,shell脚本到哪里去获得它们的环境变量呢?别忘了有些
shell脚本是通过启动一个子shell来执行的(参见第5章)。子shell可以继承父shell导出过的变量。

举例来说,如果父shell是登录shell,在/etc/profile、/etc/profile.d/*.sh和$HOME/.bashrc文件中
设置并导出了变量,用于执行脚本的子shell就能够继承这些变量。

要记住,由父shell设置但并未导出的变量都是局部变量。子shell无法继承局部变量。
对于那些不启动子shell的脚本,变量已经存在于当前shell中了。所以就算没有设置
BASH_ENV,也可以使用当前shell的局部变量和全局变量。

6.6.4 环境变量持久化

现在你已经了解了各种shell进程以及对应的环境文件,找出永久性环境变量就容易多了。也
可以利用这些文件创建自己的永久性全局变量或局部变量。

对全局环境变量来说(Linux系统中所有用户都需要使用的变量),可能更倾向于将新的或修
改过的变量设置放在/etc/profile文件中,但这可不是什么好主意。如果你升级了所用的发行版,
这个文件也会跟着更新,那你所有定制过的变量设置可就都没有了。

最好是在/etc/profile.d目录中创建一个以.sh结尾的文件。把所有新的或修改过的全局环境变
量设置放在这个文件中。

在大多数发行版中,存储个人用户永久性bash shell变量的地方是$HOME/.bashrc文件。这一
点适用于所有类型的shell进程。但如果设置了BASH_ENV变量,那么记住,除非它指向的是
$HOME/.bashrc,否则你应该将非交互式shell的用户变量放在别的地方。


说明 图形化界面组成部分(如GUI客户端)的环境变量可能需要在另外一些配置文件中设置,
这和设置bash shell环境变量的地方不一样。


想想第5章中讲过的alias命令设置就是不能持久的。你可以把自己的alias设置放在
$HOME/.bashrc启动文件中,使其效果永久化。