使用 Linux 环境变量

什么是环境变量

环境变量(environment variable)用于存储有关shell会话和工作环境的信息,以便程序或脚本能够轻松访问它们。环境变量有两类:

  • 全局变量
  • 局部变量

全局变量对shell和子shell都可见,而局部变量只对创建它们的shell可见。

全局环境变量

Linux 会在开始 bash 会话时设置一些全局环境变量,系统环境变量基本上全是大写字母,以区别普通用户环境变量。

我们可以用 envprintenv 命令:

$ env
SHELL=/bin/bash
LANGUAGE=en_US:en
PWD=/home/pi
LOGNAME=pi
XDG_SESSION_TYPE=tty
HOME=/home/pi
LANG=en_US.UTF-8
[...]

上面只列出了部分系统环境变量。我们可以用 printenv 来查看特定环境变量的值:

$ printenv HOME
/home/pi

或者用 echo 也行,不过需要在环境变量前加 $

$ echo $HOME
/home/pi

在环境变量前加 $ 实际上是取值,echo 则类似于 c 语言中的 printf。利用这个我们可以用变量来代替命令参数:

$ ls $HOME
Desktop  Downloads  Music  Public

全局变量可以用于进程的所有子shell:

$ bash
$ ps --forest
  PID TTY          TIME CMD
32424 pts/3    00:00:00 bash
  665 pts/3    00:00:00  \_ bash
  764 pts/3    00:00:00      \_ ps
$ echo $HOME
/home/pi
$ exit
exit

局部环境变量

Linux 并没有一个只显示局部环境变量的命令,set 命令会显示所有环境变量,包括局部、全局、用户自定义变量:

$ set
BASH=/bin/bash
[...]

设置用户定义变量

设置局部用户定义变量

为了区分系统变量,我们要求用户定义变量必须要小写。设置局部变量时无需声明,直接用等号赋值即可:

$ my_variable=Hello
$ echo $my_variable
Hello

赋值可以是数字或字符串,如果字符串中有空格,则需要用引号围起来(单、双均可):

$ my_variable='Hello World'
$ echo $my_variable
Hello World
$ my_variable="Hello World"
$ echo $my_variable
Hello World

另外要注意的是,变量名、等号、值之间没有空格。如果加了空格,bash shell 就会把变量当成一个单独的命令:

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

父 shell 局部变量不能在子 shell 中使用,同理子 shell 的局部变量也不能在父 shell 中使用:

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

设置全局用户定义变量

创建全局变量的方法是先创建局部变量,然后再用 export 导出到全局环境中:

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

如果在子 shell 中修改全局变量,并不会影响父 shell 中该变量的值:

#接上面
$ my_variable="NULL"
$ echo $my_variable
NULL
$ exit
exit
$ echo $my_variable
I am Global

删除环境变量

可以用 unset 删除环境变量,注意不要加 $

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

怎么判断是否加 $ 呢?如果要用到变量,就加;如果要操作变量,就不加。不过有个例外就是用 printenv 显示变量的值时不用加。

另外,如果在子 shell 中删除了全局环境变量,则只对子 shell 有效。该变量在父 shell 中依然可用。

设置 PATH 环境变量

当输入外部命令时,shell 必须搜索系统来找到对应的程序,PATH 环境变量就定义了进行命令和程序查找的目录:

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

PATH 中的目录用冒号分隔。我们可以添加新的目录:

$ PATH=$PATH:/home/pi
$ echo $PATH
/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games:/snap/bin:/home/pi

程序员比较常用的是将单点符也加入环境变量,单点符表示当前目录:

$ PATH=$PAHT:.
$ echo $PATH
/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games:/snap/bin:/home/pi:.

但这种修改只对当前 shell 有效,退出或重启后就会恢复默认。下一节会讲如何永久保持环境变量的修改效果。

定位系统环境变量

当我们启动一个 shell 时,bash 会通过运行一些文件来导入环境变量。这些文件叫做 启动文件环境文件。根据我们启动 shell 的方式,启动文件会有所不同。比如:

  • 登录时作为默认登录 shell
    • /etc/profile
    • $HOME/.bash_profile
    • $HOME/.bashrc
    • $HOME/.bash_login
    • $HOME/.profile
  • 作为非登录 shell 的交互式 shell
    • $HOME/.bashrc
  • 作为运行脚本的非交互 shell
    • $BASH_ENV

下面我们来一个一个分析。

登录 shell

/etc/profile 是 bash shell 默认的主启动文件,只要登录了 Linux 系统,bash 就会执行该文件中的命令。不同 Linux 发行版的命令不同,比如在 Debian 或 Ubuntu 上,有如下命令:

$ 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 [ "`id -u`" -eq 0 ]; then
  PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
else
  PATH="/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games"
fi
export PATH

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

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.bashec 的文件,这个文件包含了系统环境变量。

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

  1. $HOME/.bash_profile
  2. $HOME/.bash_login
  3. $HOME/.profile

这个列表中没有 $HOME/.bashrc,因为这个文件通常通过其他文件运行的。比如我的 $HOME/.profile 是这样的:

$ cat .profile
# ~/.profile: executed by the command interpreter for login shells.
# This file is not read by bash(1), if ~/.bash_profile or ~/.bash_login
# exists.
# see /usr/share/doc/bash/examples/startup-files for examples.
# the files are located in the bash-doc package.

# the default umask is set in /etc/profile; for setting the umask
# for ssh logins, install and configure the libpam-umask package.
#umask 022

# if running bash
if [ -n "$BASH_VERSION" ]; then
    # include .bashrc if it exists
    if [ -f "$HOME/.bashrc" ]; then
        . "$HOME/.bashrc"
    fi
fi

# set PATH so it includes user's private bin if it exists
if [ -d "$HOME/bin" ] ; then
    PATH="$HOME/bin:$PATH"
fi

交互式 shell

如果 bash shell 不是登录系统时启动的(比如在命令提示符敲入 bash 时启动),这种 shell 就叫 交互式 shell,它只会检查 $HOME/.bashrc 文件(但会继承父 shell 的全局变量)。

$HOME/.bashrc 有两个作用:

  1. 用于查看 /etc 目录下通用的 bashrc 文件
  2. 为用户提供一个定制自己目录别名和私有脚本函数的地方

非交互式 shell

系统执行 shell 脚本时会启动非交互 shell。它会检查 BASH_ENV 环境变量来查看要执行的启动文件。

持久化环境变量

知道了系统是如何导入环境变量后,我们可以在对应的文件中添加自己的环境变量。在大多数发行版中,存储个人用户永久的 bash shell 变量的地方是 $HOME/.bashrc 文件。

GUI 客户端的环境变量可能要在另外一些配置文件中设置

数组变量

环境变量可以作为数组使用,只需要把多个值放在括号里,值与值之间用空格分隔:

$ echo $my_list
zero
$ echo ${my_list[2]}
two
$ echo $my_list[2]
zero[2]

要显示整个数组变量,可以用 * 作为通配符放在索引的位置:

$ echo ${my_list[*]}
zero one two

可以单独改变某个索引位置的值:

$ my_list[2]=2
$ echo ${my_list[*]}
zero one 2

甚至可以用 unset 删除某个位置的值,但注意,这不会改变其他元素的索引:

$ unset my_list[1]
$ echo ${my_list[*]}
zero 2
$ echo ${my_list[1]}

$ echo ${my_list[2]}
2

也可以用 unset 删除整个数组:

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