第 6 章 使用Linux环境变量

本章内容

  • 什么是环境变量
  • 创建自己的局部变量
  • 删除环境变量
  • 默认shell环境变量
  • 设置PATH环境变量
  • 定位环境文件
  • 数组变量

Linux环境变量能帮你提升Linux shell体验。很多程序和脚本都通过环境变量来获取系统信息、存储临时数据和配置信息。在Linux系统上有很多地方可以设置环境变量,了解去哪里设置相应的环境变量很重要。
本章将带你逐步了解Linux环境变量:它们存储在哪里,怎样使用,以及怎样创建自己的环境变量。最后以数组变量的用法作结。

6.1 什么是环境变量

bash shell用一个叫作环境变量(environment variable)的特性来存储有关shell会话和工作环境的信息(这也是它们被称作环境变量的原因)。这项特性允许你在内存中存储数据,以便程序或shell中运行的脚本能够轻松访问到它们。这也是存储持久数据的一种简便方法。
在bash shell中,环境变量分为两类:

  • 全局变量
  • 局部变量

本节将描述以上环境变量,并演示怎么查看和使用它们。

说明 尽管bash shell使用一致的专有环境变量,但不同的Linux发行版经常会添加其自有的环境变量。你在本章中看到的环境变量的例子可能会跟你安装的发行版中看到的结果略微不同。如果遇到本书未讲到的环境变量,可以查看你的Linux发行版上的文档。

6.1. 1 全局环境变量

全局环境变量对于shell会话和所有生成的子shell都是可见的。局部变量则只对创建它们的shell可见。这让全局环境变量对那些所创建的子shell需要获取父shell信息的程序来说非常有用。Linux系统在你开始bash会话时就设置了一些全局环境变量(如想了解此时设置了哪些变量,请参见6.6节)。系统环境变量基本上都是使用全大写字母,以区别于普通用户的环境变量。要查看全局变量,可以使用env或printenv命令。

  1. $ printenv
  2. HOSTNAME=server01.class.edu
  3. SELINUX_ROLE_REQUESTED=
  4. TERM=xterm
  5. SHELL=/bin/bash
  6. HISTSIZE=1000
  7. [...]
  8. HOME=/home/Christine
  9. LOGNAME=Christine
  10. [...]
  11. G_BROKEN_FILENAMES=1
  12. _=/usr/bin/printenv

系统为bash shell设置的全局环境变量数目众多,我们不得不在展示的时候进行删减。其中有很多是在登录过程中设置的,另外,你的登录方式也会影响到所设置的环境变量。
要显示个别环境变量的值,可以使用printenv命令,但是不要用env命令。

$ printenv HOME
/home/Christine
$
$ env HOME
env: HOME: No such file or directory
$

也可以使用echo显示变量的值。在这种情况下引用某个环境变量的时候,必须在变量前面加上一个美元符($)。

$ echo $HOME
/home/Christine
$

在echo命令中,在变量名前加上$可不仅仅是要显示变量当前的值。它能够让变量作为命令行参数。

$ ls $HOME
Desktop Downloads Music Public test.sh
Documents junk.dat Pictures Templates Videos
$
$ ls /home/Christine
Desktop Downloads Music Public test.sh
Documents junk.dat Pictures Templates Videos
$

正如前面提到的,全局环境变量可用于进程的所有子shell。

$ bash
$
$ ps -f
UID PID PPID C STIME TTY TIME CMD
501 2017 2016 0 16:00 pts/0 00:00:00 -bash
501 2082 2017 0 16:08 pts/0 00:00:00 bash
501 2095 2082 0 16:08 pts/0 00:00:00 ps -f
$
$ echo $HOME
/home/Christine
$
$ exit
exit
$

在这个例子中,用bash命令生成一个子shell后,显示了HOME环境变量的当前值,这个值和父shell中的一模一样,都是/home/Chrisine。

6.1.2 局部环境变量

顾名思义,局部环境变量只能在定义它们的进程中可见。尽管它们是局部的,但是和全局环境变量一样重要。事实上,Linux系统也默认定义了标准的局部环境变量。不过你也可以定义自己的局部变量,如你所想,这些变量被称为用户定义局部变量。
查看局部环境变量的列表有点复杂。遗憾的是,在Linux系统并没有一个只显示局部环境变量的命令。set命令会显示为某个特定进程设置的所有环境变量,包括局部变量、全局变量 以及用户定义变量。

$ set
BASH=/bin/bash
[...]
BASH_ALIASES=()
BASH_ARGC=()
BASH_ARGV=()
BASH_CMDS=()
BASH_LINENO=()
BASH_SOURCE=()
[...]
colors=/etc/DIR_COLORS
my_variable='Hello World'
[...]
$

可以看到,所有通过printenv命令能看到的全局环境变量都出现在了set命令的输出中。但在set命令的输出中还有其他一些环境变量,即局部环境变量和用户定义变量。

说明 命令env、printenv和set之间的差异很细微。set命令会显示出全局变量、局部变量以及用户定义变量。它还会按照字母顺序对结果进行排序。env和printenv命令同set命令的区别在于前两个命令不会对变量排序,也不会输出局部变量和用户定义变量。在这种情况下,env和printenv的输出是重复的。不过env命令有一个printenv没有的功能, 这使得它要更有用一些。

6.2 设置用户定义变量

可以在bash shell中直接设置自己的变量。本节将介绍怎样在交互式shell或shell脚本程序中创建自己的变量并引用它们。

6.2.1 设置局部用户定义变量

一旦启动了bash shell(或者执行一个shell脚本),就能创建在这个shell进程内可见的局部变量了。可以通过等号给环境变量赋值,值可以是数值或字符串。

$ echo $my_variable
$ my_variable=Hello
$
$ echo $my_variable
Hello

非常简单!现在每次引用my_variable 环境变量的值,只要通过$my_variable引用即可。如果要给变量赋一个含有空格的字符串值,必须用单引号来界定字符串的首和尾。

$ my_variable=Hello  World
-bash: World: command not found
$
$ my_variable="Hello World"
$
$  echo  $my_variable
Hello World
$

没有单引号的话,bash shell会以为下一个词是另一个要执行的命令。注意,你定义的局部环境变量用的是小写字母,而到目前为止你所看到的系统环境变量都是大写字母。

窍门 所有的环境变量名均使用大写字母,这是bash shell的标准惯例。如果是你自己创建的局部变量或是shell脚本,请使用小写字母。变量名区分大小写。在涉及用户定义的局部变量时坚持使用小写字母,这能够避免重新定义系统环境变量可能带来的灾难。

记住,变量名、等号和值之间没有空格,这一点非常重要。如果在赋值表达式中加上了空格,bash shell就会把值当成一个单独的命令:

$  my_variable  =  "Hello  World"
-bash:  my_variable:  command  not  found
$

设置了局部环境变量后,就能在shell进程的任何地方使用它了。但是,如果生成了另外一个shell,它在子shell中就不可用。

$ my_variable="Hello World"
$
$ bash
$
$  echo  $my_variable

$ exit
exit
$
$  echo  $my_variable
Hello World
$

在这个例子中生成了一个子shell。在子shell中无法使用用户定义变量my_variable。通过命令echo $my_variable所返回的空行就能够证明这一点。当你退出子shell并回到原来的shell时, 这个局部环境变量依然可用。
类似地,如果你在子进程中设置了一个局部变量,那么一旦你退出了子进程,那个局部环境变量就不可用。

$  echo  $my_child_variable

$ bash
$
$ my_child_variable="Hello Little World"
$
$  echo  $my_child_variable
Hello Little World
$
$ exit
exit
$
$  echo  $my_child_variable

$

当我们回到父shell时,子shell中设置的局部变量就不存在了。可以通过将局部的用户定义变量变成全局变量来改变这种情况。

6.2.2 设置全局环境变量

在设定全局环境变量的进程所创建的子进程中,该变量都是可见的。创建全局环境变量的方法是先创建一个局部环境变量,然后再把它导出到全局环境中。这个过程通过export命令来完成,变量名前面不需要加$。

$  my_variable="I  am  Global  now"
$
$ export my_variable
$
$  echo  $my_variable
I am Global now
$
$ bash
$
$  echo  $my_variable
I am Global now
$
$ exit
exit
$
$  echo  $my_variable
I am Global now
$

在定义并导出局部环境变量my_variable后,bash命令启动了一个子shell。在这个子shell 中能够正确的显示出变量my_variable的值。该变量能够保留住它的值是因为export命令使其变成了全局环境变量。
修改子shell中全局环境变量并不会影响到父shell中该变量的值。

$  my_variable="I  am  Global  now"
$ export my_variable
$
$  echo  $my_variable
I am Global now
$
$ bash
$
$  echo  $my_variable
I am Global now
$
$  my_variable="Null"
$
$  echo  $my_variable
Null
$
$ exit
exit
$
$  echo  $my_variable
I am Global now
$

在定义并导出变量my_variable后,bash命令启动了一个子shell。在这个子shell中能够正 确显示出全局环境变量my_variable的值。子shell随后改变了这个变量的值。但是这种改变仅在子shell中有效,并不会被反映到父shell中。
子shell甚至无法使用export命令改变父shell中全局环境变量的值。

$  my_variable="I  am  Global  now"
$ export my_variable
$
$  echo  $my_variable
I am Global now
$
$ bash
$
$  echo  $my_variable
I am Global now
$
$  my_variable="Null"
$
$ export my_variable
$
$  echo  $my_variable
Null
$
$ exit
exit
$
$  echo  $my_variable
I am Global now
$

尽管子shell重新定义并导出了变量my_variable,但父shell中的my_variable变量依然保 留着原先的值。

6.3 删除环境变量

当然,既然可以创建新的环境变量,自然也能删除已经存在的环境变量。可以用unset命令完成这个操作。在unset命令中引用环境变量时,记住不要使用$。

$  echo  $my_variable
I am Global now
$
$ unset my_variable
$
$  echo  $my_variable

$

窍门 在涉及环境变量名时,什么时候该使用$,什么时候不该使用$,实在让人摸不着头脑。记住一点就行了:如果要用到变量,使用$;如果要操作变量,不使用$。这条规则的一个例外就是使用printenv显示某个变量的值。

在处理全局环境变量时,事情就有点棘手了。如果你是在子进程中删除了一个全局环境变量, 这只对子进程有效。该全局环境变量在父进程中依然可用。

$  my_variable="I  am  Global  now"
$
$ export my_variable
$
$  echo  $my_variable
I am Global now
$
$ bash
$
$  echo  $my_variable
I am Global now
$
$ unset my_variable
$
$  echo  $my_variable

$ exit
exit
$
$  echo  $my_variable
I am Global now
$

和修改变量一样,在子shell中删除全局变量后,你无法将效果反映到父shell中。

6.4 默认的 shell 环境变量

默认情况下,bash shell会用一些特定的环境变量来定义系统环境。这些变量在你的Linux系统上都已经设置好了,只管放心使用。bash shell源自当初的Unix Bourne shell,因此也保留了Unix Bourne shell里定义的那些环境变量。
表6-1列出了bash shell提供的与Unix Bourne shell兼容的环境变量。
表6-1 bash shell支持的Bourne变量

变 量 描 述
CDPATH 冒号分隔的目录列表,作为cd命令的搜索路径
HOME 当前用户的主目录
IFS shell 用来将文本字符串分割成字段的一系列字符
MAIL 当前用户收件箱的文件名(bash shell会检查这个文件,看看有没有新邮件)
MAILPATH 冒号分隔的当前用户收件箱的文件名列表(bash shell会检查列表中的每个文件,看看有没有新邮件)
OPTARG getopts 命令处理的最后一个选项参数值
OPTIND getopts 命令处理的最后一个选项参数的索引号
PATH shell 查找命令的目录列表,由冒号分隔
PS1 shell 命令行界面的主提示符
PS2 shell 命令行界面的次提示符

除了默认的Bourne的环境变量,bash shell还提供一些自有的变量,如表6-2所示。
表6-2 bash shell环境变量

变 量 描 述
BASH 当前shell实例的全路径名
BASH_ALIASES 含有当前已设置别名的关联数组
BASH_ARGC 含有传入子函数或shell脚本的参数总数的数组变量
BASH_ARCV 含有传入子函数或shell脚本的参数的数组变量
BASH_CMDS 关联数组,包含shell执行过的命令的所在位置
BASH_COMMAND shell正在执行的命令或马上就执行的命令
BASH_ENV 设置了的话,每个bash脚本会在运行前先尝试运行该变量定义的启动文件
BASH_EXECUTION_STRING 使用bash -c选项传递过来的命令
BASH_LINENO 含有当前执行的shell函数的源代码行号的数组变量
BASH_REMATCH 只读数组,在使用正则表达式的比较运算符=~进行肯定匹配(positive match)时,包含了匹配到的模式和子模式
BASH_SOURCE 含有当前正在执行的shell函数所在源文件名的数组变量
BASH_SUBSHELL 当前子shell环境的嵌套级别(初始值是0)
BASH_VERSINFO 含有当前运行的bash shell的主版本号和次版本号的数组变量
BASH_VERSION 当前运行的bash shell的版本号
BASH_XTRACEFD 若设置成了有效的文件描述符(0、1、2),则’set -x’调试选项生成的跟踪输出可被重定向。通常用来将跟踪输出到一个文件中
BASHOPTS 当前启用的bash shell选项的列表
BASHPID 当前bash进程的PID
COLUMNS 当前bash shell实例所用终端的宽度
COMP_CWORD COMP_WORDS变量的索引值,后者含有当前光标的位置
COMP_LINE 当前命令行
COMP_POINT 当前光标位置相对于当前命令起始的索引
COMP_KEY 用来调用shell函数补全功能的最后一个键
COMP_TYPE 一个整数值,表示所尝试的补全类型,用以完成shell函数补全
COMP_WORDBREAKS Readline库中用于单词补全的词分隔字符
COMP_WORDS 含有当前命令行所有单词的数组变量
COMPREPLY 含有由shell函数生成的可能填充代码的数组变量
COPROC 占用未命名的协进程的I/O文件描述符的数组变量
DIRSTACK 含有目录栈当前内容的数组变量
EMACS 设置为’t’时,表明emacs shell缓冲区正在工作,而行编辑功能被禁止
ENV 如果设置了该环境变量,在bash shell脚本运行之前会先执行已定义的启动文件(仅用于当bash
EUID 当前用户的有效用户ID(数字形式)
FCEDIT 供fc命令使用的默认编辑器
FIGNORE 在进行文件名补全时可以忽略后缀名列表,由冒号分隔
FUNCNAME 当前执行的shell函数的名称
FUNCNEST 当设置成非零值时,表示所允许的最大函数嵌套级数(一旦超出,当前命令即被终止)
GLOBIGNORE 冒号分隔的模式列表,定义了在进行文件名扩展时可以忽略的一组文件名
GROUPS 含有当前用户属组列表的数组变量
histchars 控制历史记录扩展,最多可有3个字符
HISTCMD 当前命令在历史记录中的编号
HISTCONTROL 控制哪些命令留在历史记录列表中
HISTFILE 保存shell历史记录列表的文件名(默认是.bash_history)
HISTFILESIZE 最多在历史文件中存多少行
HISTTIMEFORMAT 如果设置了且非空,就用作格式化字符串,以显示bash历史中每条命令的时间戳
HISTIGNORE 由冒号分隔的模式列表,用来决定历史文件中哪些命令会被忽略
HISTSIZE 最多在历史文件中存多少条命令
HOSTFILE shell在补全主机名时读取的文件名称
HOSTNAME 当前主机的名称
HOSTTYPE 当前运行bash
IGNOREEOF shell在退出前必须收到连续的EOF字符的数量(如果这个值不存在,默认是1)
INPUTRC Readline初始化文件名(默认是.inputrc)
LANG shell的语言环境类别
LC_ALL 定义了一个语言环境类别,能够覆盖LANG变量
LC_COLLATE 设置对字符串排序时用的排序规则
LC_CTYPE 决定如何解释出现在文件名扩展和模式匹配中的字符
LC_MESSAGES 在解释前面带有$的双引号字符串时,该环境变量决定了所采用的语言环境设置
LC_NUMERIC 决定着格式化数字时采用的语言环境设置
LINENO 当前执行的脚本的行号
LINES 定义了终端上可见的行数
MACHTYPE 用“CPU-公司-系统”(CPU-company-system)格式定义的系统类型
MAPFILE 一个数组变量,当mapfile命令未指定数组变量作为参数时,它存储了mapfile所读入的文本
MAILCHECK shell查看新邮件的频率(以秒为单位,默认值是60)
OLDPWD shell之前的工作目录
OPTERR 设置为1时,bash
OSTYPE 定义了shell所在的操作系统
PIPESTATUS 含有前台进程的退出状态列表的数组变量
POSIXLY_CORRECT 设置了的话,bash会以POSIX模式启动
PPID bash shell父进程的PID
PROMPT_COMMAND 设置了的话,在命令行主提示符显示之前会执行这条命令
PROMPT_DIRTRIM 用来定义当启用了\w或\W提示符字符串转义时显示的尾部目录名的数量。被删除的目录名会用一组英文句点替换
PS3 select命令的提示符
PS4 如果使用了bash的-x选项,在命令行之前显示的提示信息
PWD 当前工作目录
RANDOM 返回一个0~32767的随机数(对其的赋值可作为随机数生成器的种子)
READLINE_LINE 当使用bind –x命令时,存储Readline缓冲区的内容
READLINE_POINT 当使用bind –x命令时,表示Readline缓冲区内容插入点的当前位置
REPLY read命令的默认变量
SECONDS 自从shell启动到现在的秒数(对其赋值将会重置计数器)
SHELL bash
SHELLOPTS 已启用bash shell选项列表,列表项之间以冒号分隔
SHLVL shell的层级;每次启动一个新bash shell,该值增加1
TIMEFORMAT 指定了shell的时间显示格式
TMOUT select和read命令在没输入的情况下等待多久(以秒为单位)。默认值为0,表示无限长
TMPDIR 目录名,保存bash
UID 当前用户的真实用户ID(数字形式)

你可能已经注意到,不是所有的默认环境变量都会在运行set命令时列出。尽管这些都是默认环境变量,但并不是每一个都必须有一个值。

6.5 设置 PATH 环境变量

当你在shell命令行界面中输入一个外部命令时(参见第5章),shell必须搜索系统来找到对应的程序。PATH环境变量定义了用于进行命令和程序查找的目录。在本书所用的Ubuntu系统中, PATH环境变量的内容是这样的:

$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:
/sbin:/bin:/usr/games:/usr/local/games
$

输出中显示了有8个可供shell用来查找命令和程序。PATH中的目录使用冒号分隔。
如果命令或者程序的位置没有包括在PATH变量中,那么如果不使用绝对路径的话,shell是没 法找到的。如果shell找不到指定的命令或程序,它会产生一个错误信息:

$ myprog
-bash: myprog: command not found
$

问题是,应用程序放置可执行文件的目录常常不在PATH环境变量所包含的目录中。解决的 办法是保证PATH环境变量包含了所有存放应用程序的目录。
可以把新的搜索目录添加到现有的PATH环境变量中,无需从头定义。PATH中各个目录之间是用冒号分隔的。你只需引用原来的PATH值,然后再给这个字符串添加新目录就行了。可以参 考下面的例子。

$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:
/sbin:/bin:/usr/games:/usr/local/games
$
$  PATH=$PATH:/home/christine/Scripts
$
$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/ games:/usr/local/games:/home/christine/Scripts
$
$ myprog
The factorial of 5 is 120.
$

将目录加到PATH环境变量之后,你现在就可以在虚拟目录结构中的任何位置执行程序。

$ cd /etc
$
$ myprog
The factorial of 5 is 120
$

窍门 如果希望子shell也能找到你的程序的位置,一定要记得把修改后的PATH环境变量导出。

程序员通常的办法是将单点符也加入PATH环境变量。该单点符代表当前目录(参见第3章)。

$ PATH=$PATH:.
$
$   cd   /home/christine/Old_Scripts
$
$ myprog2
The factorial of 6 is 720
$

对PATH变量的修改只能持续到退出或重启系统。这种效果并不能一直持续。在下一节中, 你会学到如何永久保持环境变量的修改效果。

6.6 定位系统环境变量

环境变量在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的主启动文件。系统上的每个用户登录时都会执行这个启动文件。
6

说明 要留意的是有些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系统上,它看起来是这样的:

$ cat /etc/profile
# /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文件还在内部导出了一些系统环境变量。

$  cat  /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环境变量。

  1. $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文件。这是因为该文件通常通过其他文件运行的。
6

窍门 记住,$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
$

举例来说,如果父shell是登录shell,在/etc/profile、/etc/profile.d/*.sh和$HOME/.bashrc文件中设置并导出了变量,用于执行脚本的子shell就能够继承这些变量。那如果BASH_ENV变量没有设置,shell脚本到哪里去获得它们的环境变量呢?别忘了有些 shell脚本是通过启动一个子shell来执行的(参见第5章)。子shell可以继承父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启动文件中,使其效果永久化。

6.7 数组变量

环境变量有一个很酷的特性就是,它们可作为数组使用。数组是能够存储多个值的变量。这些值可以单独引用,也可以作为整个数组来引用。
要给某个环境变量设置多个值,可以把值放在括号里,值与值之间用空格分隔。

$ mytest=(one two three four five)
$

没什么特别的地方。如果你想把数组像普通的环境变量那样显示,你会失望的。

$ echo $mytest
one
$

只有数组的第一个值显示出来了。要引用一个单独的数组元素,就必须用代表它在数组中位置的数值索引值。索引值要用方括号括起来。

$ echo  ${mytest[2]}
three
$

窍门 环境变量数组的索引值都是从零开始。这通常会带来一些困惑。

要显示整个数组变量,可用星号作为通配符放在索引值的位置。

$ echo  ${mytest[*]}
one two three four five
$

也可以改变某个索引值位置的值。

$  mytest[2]=seven
$
$ echo  ${mytest[*]}
one two seven four five
$

甚至能用unset命令删除数组中的某个值,但是要小心,这可能会有点复杂。看下面的例子。

$  unset  mytest[2]
$
$ echo  ${mytest[*]}
one two four five
$
$  echo  ${mytest[2]}

$  echo  ${mytest[3]}
four
$

这个例子用unset命令删除在索引值为2的位置上的值。显示整个数组时,看起来像是索引里面已经没这个索引了。但当专门显示索引值为2的位置上的值时,就能看到这个位置是空的。
最后,可以在unset命令后跟上数组名来删除整个数组。

$  unset  mytest
$
$ echo  ${mytest[*]}

$

有时数组变量会让事情很麻烦,所以在shell脚本编程时并不常用。对其他shell而言,数组变量的可移植性并不好,如果需要在不同的shell环境下从事大量的脚本编写工作,这会带来很多不便。有些bash系统环境变量使用了数组(比如BASH_VERSINFO),但总体上不会太频繁用到。

6.8 小结

本章介绍了Linux的环境变量。全局环境变量可以在对其作出定义的父进程所创建的子进程中使用。局部环境变量只能在定义它们的进程中使用。
Linux系统使用全局环境变量和局部环境变量存储系统环境信息。可以通过shell的命令行界面或者在shell脚本中访问这些信息。bash shell沿用了最初Unix Bourne shell定义的那些系统环境变量,也支持很多新的环境变量。PATH环境变量定义了bash shell在查找可执行命令时的搜索目录。可以修改PATH环境变量来添加自己的搜索目录(甚至是当前目录符号),以方便程序的运行。也可以创建自用的全局和局部环境变量。一旦创建了环境变量,它在整个shell会话过程中就都是可用的。
bash shell会在启动时执行几个启动文件。这些启动文件包含了环境变量的定义,可用于为每个bash会话设置标准环境变量。每次登录Linux系统,bash shell都会访问/etc/profile启动文件以及3 个针对每个用户的本地启动文件:$HOME/.bash_profile、$HOME/.bash_login和$HOME/.profile。用户可以在这些文件中定制自己想要的环境变量和启动脚本。
最后,我们还讨论了环境变量数组。这些环境变量可在单个变量中包含多个值。你可以通过指定索引值来访问其中的单个值,或是通过环境变量数组名来引用所有的值。
下章将会深入介绍Linux文件的权限。对Linux新手来说,这可能是最难懂的。然而要写出优秀的shell脚本,就必须明白文件权限的工作原理以及如何在Linux系统中使用它们。