1. 简介

Bash Shell 使用「环境变量」来存储有关 Shell 会话和工作环境的信息。环境变量分为两类:

  • 全局环境变量:全局环境变量对于所有 Shell 会话和所生成的子 Shell 、孩子 Shell 都是可见的。
  • 局部环境变量:局部环境变量只对创建它们的 Shell 和子 Shell 可见。
  • 本地环境变量:本地环境变量只能在定义它们的函数内部以及子函数中可见。

【注】有关子 Shell 和孩子 Shell 的详细介绍参见 LinuxShell父子关系概述

环境变量区分大小写,系统环境变量基本都是使用全大写字母,以区别于用户自定义的环境变量,因此用户自定义的环境变量最好都使用小写。

2. 环境变量

2.1 全局环境变量

查看全局变量可以使用 envprintenv 命令。

1
2
3
env                 # 查看所有全局变量
printenv # 查看所有全局变量
printenv <variable> # 查看单个全局变量 <variable>

【注】envprintenv 只会输出全局变量,而且也不会对输出的全局变量进行排序。

2.2 局部环境变量

局部环境变量只能在定义它们的进程中可见。查看局部变量的列表有些复杂,Linux 系统并没有一个只显示局部环境变量的命令。可以使用 set 命令显示某个特定进程设置的所有环境变量,包括系统局部变量、系统全局变量和用户自定义环境变量。

1
set

【注】set 命令会将变量按照字母顺序进行排序,且只显示值非空的环境变量。除了输出所有的环境变量外,使用不带参数的 set 命令还会输出当前环境所有的函数。

2.4 本地环境变量

本地环境变量只能在函数内部被定义和使用,它们只能在定义它们的函数内部以及子函数中可见。

3. 设置用户定义变量

创建环境变量时,变量名的命名需要遵守以下规则:

  • 字母、数字和下划线字符组成。
  • 首字母不能是数字。
  • 不能出现空格和标点符号。

3.1 设置局部用户定义变量

通过使用等号来设置局部环境变量,并给其赋值,值可以是数值或者字符串。基本格式如下:

1
var=value          	# var 为局部环境变量,value 为其值
  • 变量名、等号和数值之间不能有空格。
  • 在当进程定义的局部变量只能在当前进程使用。
  • Bash Shell 没有数据类型的概念,所有的变量值都是字符串。

Bash Shell 扩展了局部环境变量赋值语句,通过在赋值语句后接一条命令,则可以实现该局部变量仅在命令执行环境中有效,一旦命令执行完局部变量也将失效。

1
var=value command

3.2 设置全局用户定义变量

创建全局环境变量的方法是先创建一个局部变量,再通过 export 命令导出到全局环境中。

1
2
var=value
export var
  • 在当前进程中创建的全局变量在当前进程的所有子进程中都是可见的。
  • 在子进程中创建的全局变量在当前进程是不可见的。
  • 在子进程中修改的全局变量只对子进程及其更下层进程有效,对父进程是无效的,即子进程的修改无法反映到父进程中,父进程的全局变量仍然保持原样。

总而言之,即全局变量的增删改查都只是向下单层单向独立继承。

其实也好理解,因为不同进程之间的各个参数都是独立的,因此只有在创建子进程的时候存在父进程到子进程的变量继承,创建后两个进程之间的变量就是完全独立的,互不影响。

3.3 设置本地环境变量

local 命令用于创建「本地环境变量」。其语法格式如下:

1
local OPTION VARIABLE=value

local 的选项参数 OPTION 同下文介绍的 declare,但诸如 -x 参数是无效的。

3.4 读取环境变量

读取环境变量时,直接在变量前加上 $ 即可。

1
echo $HOME
  • 当变量不存在是,Bash Shell 不会报错,而是当作空值处理。
  • 读取变量名时,如果变量名和其他字符连在一起,需要使用 {} 包裹变量名以明确变量名。
1
2
a=foo
echo ${a}_file
  • 若一个变量值为另一个变量名字符串,如果想要使用另一个变量名的值,可以使用 ${!varname} 语法来读取。
1
2
var="USER"
echo ${!var}

3.5 删除环境变量

在 Bash Shell 使用 unset 命令来删除环境变量。

1
unset var
  • 和修改全局变量类似,在子进程中删除全局变量也无法反映到父进程中。

3.6 环境变量持久化

在登入 Linux 系统启动一个 Bash Shell 时,默认情况下 Bash 会在几个文件中查找命令。这些文件称为「启动文件」或「环境文件」。Bash 检查的启动文件取决于你启动 Bash Shell 的方式。启动 Bash Shell 有三种方式:

  • 登录时作为登录 Shell
  • 作为非登录 Shell 的交互式 Shell
  • 作为运行脚本的非交互式 Shell

而要让环境变量持久化便是将环境变量写入启动文件,这样每当启动 Bash Shell 后就会自动载入写入的环境变量从而达到持久化效果。关于这三种不同的 Shell 登录方式及其启动文件的详细介绍可参考 LinuxShell分类

【注】有些 Linux 发行版使用了「可拆卸认证模块」(PAM),在这种情况下,PAM 文件会在 Bash Shell 启动之前处理,这些文件中也可能会包含环境变量。PAM 文件包括 /etc/environment 文件和 $HOME/.pam_environment 文件。PAM 更多相关信息可参考官方网址

4. 数组变量

上文讲到环境变量都是单变量,其值可以是字符串和数值。而其实 Bash Shell 还提供了定义数组变量的功能。

4.1 定义数组变量

1
array=(val1 val2 ... valn)

其中,array 为数组变量,使用 () 来定义其值,值与值之间用空格分隔。

4.2 查询数组变量

  • 查询数组变量的单个元素值可以使用索引查找(索引值是从 0 开始的),比如查找数组第 3 个元素值:
1
echo ${array[2]}
  • 查询整个数组变量可用 * 作为通配符作为索引值:
1
echo ${array[*]}

【注】直接使用数组变量并不能查询到整个数组变量,而是只查询到其第一个元素值。和 C 语言中的数组类似,即数组变量指向的是数组的第一个元素。

1
2
3
echo $array
# 等价于
echo ${array[0]}

4.3 修改数组变量

  • 修改数组变量单个元素同样可以使用索引值:
1
array[2]=newval3
  • 修改整个数组变量和定义数组变量完全一样:
1
array=(newval1 newval2 ... newvalm)

4.4 删除数组变量

  • 删除数组变量单个元素同样可以使用索引值:
1
unset array[2]

【注】删除数组变量的单个元素后,虽然直接显示整个数组变量是没有被删除的元素,但如果专门查询该元素会发现,其实不是真的把该索引对应的数组元素剔除了,而是把它置空了,因此显示该元素时打印了空值,所以显示结果相看起来是没有该元素的。

  • 删除整个数组变量:
1
unset array

5. 默认的 Shell 环境变量

Bash Shell 源自早期的 Unix Bourne Shell,因此继承了 Unix Bourne Shell 定义的一些默认的环境变量。

5.1 Bash Shell 支持的 Unix Bourne Shell 环境变量

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

5.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 Shell 以 POSIX 模式被调用时)
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 Shell 的机器
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 Shell 会显示 getopts 命令产生的错误
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 Shell 的全路径名
SHELLOPTS 已启用 Bash Shell 选项列表,列表项之间以冒号分隔
SHLVL Shell 的层级,每次启动一个新 Bash Shell,该值增加 1
TIMEFORMAT 指定了 Shell 的时间显示格式
TMOUT selectread 命令在没输入的情况下等待多久(以秒为单位)。默认值为 0,表示无限长
TMPDIR 目录名,保存 Bash Shell 创建的临时文件
UID 当前用户的真实用户 ID(数字形式)

6. 特殊环境变量

特殊变量 说明
$0$9 位置参数,$0 是命令名,$1$9 是命令参数
$# 记录脚本运行时携带的命令参数个数
$* 用双引号括起后,将所有命令参数当作单个单词保存,即看作一个整体
$@ 用双引号括起后,将所有命令参数当作字符串中的多个独立单词保存,即看作多个个体
$? 记录最近执行的前台程序的退出状态
$- 记录了当前 Shell 的选项
$$ 当前 Shell 的 PID
$! 记录最近执行的后台进程的 PID
$_ 记录上一个命令的最后一个参数

更多详细介绍参见下文小节。

6.1 $0$9

$0$9 为命令行的位置参数,$0 是命令名,$1$9 是命令参数。

6.2 $#

$# 记录了脚本运行时携带的命令参数个数。

6.3 $*

  • $* 未用双引号括起时,即 $*:将所有位置参数扩展成独立的多个个体,而且若出现用引号括起的参数时,则会将引号忽略来看待。
  • $* 用双引号括起时,即 "$*":将所有命令参数当作单个单词保存,即看作一个整体。

6.4 $@

  • $@ 未用双引号括起时,即 $@:等同于 $*
  • $@ 用双引号括起时,即 "$@":将所有命令参数当作字符串中的多个独立单词保存,即看作多个个体,而且若出现用引号括起的参数时,则将其当作单个参数。

【注】$*$@ 的使用很容易混淆,为了更好地理解可以参见以下例子:

  • 首先定义脚本文件 test.sh
1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/bash

main()
{
echo 'MAIN sees ' $# ' args'
}

main $*
main $@

main "$*"
main "$@"
  • 然后运行该脚本文件:
1
./test.sh 'a b c' d e
  • 最后输出结果如下:
1
2
3
4
MAIN sees  5  args
MAIN sees 5 args
MAIN sees 1 args
MAIN sees 3 args

6.5 $?

$? 记录了最近执行的前台程序的退出状态。

6.6 $-

$- 记录了当前 Bash Shell 的选项标志。Bash Shell 的选项标志是在启动时或以内建命令 set 指定的,或者是 shell 自身设置的(例如选项 -i)。有关 Bash Shell 选项选项参数更详细介绍可参考 Advanced Bash-Scripting Guide 或官方手册 man bash

在本地直接查看 $- 值可以发现其为 himBH,关于其详细解释可参见 What do the characters in the bash environment variable $- mean?

6.7 $$

$$ 记录了当前 Shell 的 PID。需要注意的是,在当前 Shell 创建的子 Shell 中,指的仍然当前父 Shell 的 PID;而在孩子 Shell 中则是孩子 Shell 的 PID。这是因为子 Shell 继承了父 Shell 的所有环境变量,而孩子 Shell 只继承了父 Shell 的全局环境变量。详细介绍参见 LinuxShell父子关系概述

6.8 $!

$! 记录了最近执行的后台进程的 PID。

7. declare 命令

除了使用上文提到的简单创建变量的语法,Bash Shell 还提供了 declare 命令用来扩展变量定义语法。

  • declare 命令可以声明一些特殊类型的变量,即为变量设置一些限制。
  • declare 命令还可以查看声明的变量和函数。

7.1 语法

declare 命名创建变量的语法格式如下:

1
declare OPTION VARIABLE=value

declare 命令的主要参数(OPTION)如下:

  • -a:声明数组变量。
  • -f:输出所有函数定义。
  • -F:输出所有函数名。
  • -i:声明整数变量。
  • -l:声明变量为小写字母。
  • -p:查看变量信息。
  • -r:声明只读变量。
  • -u:声明变量为大写字母。
  • -x:该变量输出为全局环境变量。

7.2 详解

  • declare 命令如果用在函数中,声明的变量只在函数内有效,等同于 local 命令。
  • declare 命令不带任何参数时,输出当前环境所有的环境变量和函数,等同于不带任何参数的 set 命令。

8. readonly 命令

readonly 命令等同于 declare -r,用来声明只读变量,不能改变边变量值,也不能 unset 变量。

8.1 语法

readonly 命名创建变量的语法格式如下:

1
readonly OPTION VARIABLE=value

declare 命令的可选参数(OPTION)如下:

  • -f:声明的变量为函数名。
  • -p:打印出所有的只读变量。
  • -a:声明的变量为数组。

附录

参考资料来源: