Bash 变量

阮一峰 ... Linux
  • Bash
大约 11 分钟

Bash 变量分成环境变量和自定义变量两类。

# 简介

环境变量是 Bash 环境自带的变量,进入 Shell 时已经定义好了,可以直接使用。它们通常是系统定义好的,也可以由用户从父 Shell 传入子 Shell。

env 命令或 printenv 命令,可以显示所有环境变量。

$ env
# 或者
$ printenv
1
2
3

下面是一些常见的环境变量。

  • BASHPID: Bash 进程的进程 ID。
  • BASHOPTS: 当前 Shell 的参数,可以用 shopt 命令修改。
  • DISPLAY: 图形环境的显示器名字,通常是 :0,表示 X Server 的第一个显示器。
  • EDITOR: 默认的文本编辑器。
  • HOME: 用户的主目录。
  • HOST: 当前主机的名称。
  • IFS: 词与词之间的分隔符,默认为空格。
  • LANG: 字符集以及语言编码,比如 zh_CN.UTF-8
  • PATH: 由冒号分开的目录列表,当输入可执行程序名后,会搜索这个目录列表。
  • PS1: Shell 提示符。
  • PS2: 输入多行命令时,次要的 Shell 提示符。
  • PWD: 当前工作目录。
  • RANDOM: 返回一个 0 到 32767 之间的随机数。
  • SHELL: Shell 的名字。
  • SHELLOPTS: 启动当前 Shell 的 set 命令的参数,参见《set 命令》一章。
  • TERM: 终端类型名,即终端仿真器所用的协议。
  • UID: 当前用户的 ID 编号。
  • USER: 当前用户的用户名。

很多环境变量很少发生变化,而且是只读的,可以视为常量。由于它们的变量名全部都是大写,所以传统上,如果用户要自己定义一个常量,也会使用全部大写的变量名。

注意,Bash 变量名区分大小写,HOMEhome 是两个不同的变量。

查看单个环境变量的值,可以使用 printenv 命令或 echo 命令。

$ printenv PATH
# 或者
$ echo $PATH
1
2
3

注意,printenv 命令后面的变量名,不用加前缀 $

自定义变量是用户在当前 Shell 里面自己定义的变量,必须先定义后使用,而且仅在当前 Shell 可用。一旦退出当前 Shell,该变量就不存在了。

set 命令可以显示所有变量(包括环境变量和自定义变量),以及所有的 Bash 函数。

set
1

# 创建变量

用户创建变量的时候,变量名必须遵守下面的规则。

  • 字母、数字和下划线字符组成。
  • 第一个字符必须是一个字母或一个下划线,不能是数字。
  • 不允许出现空格和标点符号。

变量声明的语法如下。

variable=value
1

上面命令中,等号左边是变量名,右边是变量。注意,等号两边不能有空格。

如果变量的值包含空格,则必须将值放在引号中。

myvar="hello world"
1

Bash 没有数据类型的概念,所有的变量值都是字符串。

下面是一些自定义变量的例子。

a=z                     # 变量 a 赋值为字符串 z
b="a string"            # 变量值包含空格,就必须放在引号里面
c="a string and $b"     # 变量值可以引用其他变量的值
d="\t\ta string\n"      # 变量值可以使用转义字符
e=$(ls -l foo.txt)      # 变量值可以是命令的执行结果
f=$((5 * 7))            # 变量值可以是数学运算的结果
1
2
3
4
5
6

变量可以重复赋值,后面的赋值会覆盖前面的赋值。

$ foo=1
$ foo=2
$ echo $foo
2
1
2
3
4

上面例子中,变量 foo 的第二次赋值会覆盖第一次赋值。

# 读取变量

读取变量的时候,直接在变量名前加上 $ 就可以了。

$ foo=bar
$ echo $foo
bar
1
2
3

每当 Shell 看到以 $ 开头的单词时,就会尝试读取这个变量名对应的值。

如果变量不存在,Bash 不会报错,而会输出空字符。

由于 $ 在 Bash 中有特殊含义,把它当作美元符号使用时,一定要非常小心,

$ echo The total is $100.00
The total is 00.00
1
2

上面命令的原意是输入 $100,但是 Bash 将 $1 解释成了变量,该变量为空,因此输入就变成了 00.00。所以,如果要使用 $ 的原义,需要在 $ 前面放上反斜杠,进行转义。

$ echo The total is \$100.00
The total is $100.00
1
2

读取变量的时候,变量名也可以使用花括号 {} 包围,比如 $a 也可以写成 ${a}。这种写法可以用于变量名与其他字符连用的情况。

$ a=foo
$ echo $a_file

$ echo ${a}_file
foo_file
1
2
3
4
5

上面代码中,变量名 a_file 不会有任何输出,因为 Bash 将其整个解释为变量,而这个变量是不存在的。只有用花括号区分 $a,Bash 才能正确解读。

事实上,读取变量的语法 $foo,可以看作是 ${foo} 的简写形式。

如果变量的值本身也是变量,可以使用 ${!varname} 的语法,读取最终的值。

$ myvar=USER
$ echo ${!myvar}
ruanyf
1
2
3

上面的例子中,变量 myvar 的值是 USER${!myvar} 的写法将其展开成最终的值。

# 删除变量

unset 命令用来删除一个变量。

unset NAME
1

这个命令不是很有用。因为不存在的 Bash 变量一律等于空字符串,所以即使 unset 命令删除了变量,还是可以读取这个变量,值为空字符串。

所以,删除一个变量,也可以将这个变量设成空字符串。

foo=''
foo=
1
2

上面两种写法,都是删除了变量 foo。由于不存在的值默认为空字符串,所以后一种写法可以在等号右边不写任何值。

# 输出变量,export 命令

用户创建的变量仅可用于当前 Shell,子 Shell 默认读取不到父 Shell 定义的变量。为了把变量传递给子 Shell,需要使用 export 命令。这样输出的变量,对于子 Shell 来说就是环境变量。

export 命令用来向子 Shell 输出变量。

NAME=foo
export NAME
1
2

上面命令输出了变量 NAME。变量的赋值和输出也可以在一个步骤中完成。

export NAME=value
1

上面命令执行后,当前 Shell 及随后新建的子 Shell,都可以读取变量 $NAME

子 Shell 如果修改继承的变量,不会影响父 Shell。

# 输出变量 $foo
$ export foo=bar

# 新建子 Shell
$ bash

# 读取 $foo
$ echo $foo
bar

# 修改继承的变量
$ foo=baz

# 退出子 Shell
$ exit

# 读取 $foo
$ echo $foo
bar
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

上面例子中,子 Shell 修改了继承的变量 $foo,对父 Shell 没有影响。

# 特殊变量

Bash 提供一些特殊变量。这些变量的值由 Shell 提供,用户不能进行赋值。

(1) $?

$? 为上一个命令的退出码,用来判断上一个命令是否执行成功。返回值是 0,表示上一个命令执行成功;如果是非零,上一个命令执行失败。

$ ls doesnotexist
ls: doesnotexist: No such file or directory

$ echo $?
1
1
2
3
4
5

上面例子中,ls命令查看一个不存在的文件,导致报错。$? 为 1,表示上一个命令执行失败。

(2) $$

$$ 为当前 Shell 的进程 ID。

$ echo $$
10662
1
2

这个特殊变量可以用来命名临时文件。

LOGFILE=/tmp/output_log.$$
1

(3) $_

$_ 为上一个命令的最后一个参数。

$ grep dictionary /usr/share/dict/words
dictionary

$ echo $_
/usr/share/dict/words
1
2
3
4
5

(4) $!

$! 为最近一个后台执行的异步命令的进程 ID。

$ firefox &
[1] 11064

$ echo $!
11064
1
2
3
4
5

上面例子中,firefox 是后台运行的命令,$! 返回该命令的进程 ID。

(5) $0

$0 为当前 Shell 的名称(在命令行直接执行时)或者脚本名(在脚本中执行时)。

$ echo $0
bash
1
2

上面例子中,$0 返回当前运行的是 Bash。

(6) $-

$- 为当前 Shell 的启动参数。

$ echo $-
himBHs
1
2

(7) $@$#

$@$# 表示脚本的参数数量,参见脚本一章。

# 变量的默认值

Bash 提供四个特殊语法,跟变量的默认值有关,目的是保证变量不为空。

${varname:-word}
1

上面语法的含义是,如果变量 varname 存在且不为空,则返回它的值,否则返回 word。它的目的是返回一个默认值,比如 ${count:-0} 表示变量 count 不存在时返回 0

${varname:=word}
1

上面语法的含义是,如果变量 varname 存在且不为空,则返回它的值,否则将它设为 word,并且返回 word。它的目的是设置变量的默认值,比如 ${count:=0} 表示变量 count 不存在时返回 0,且将 count 设为 0

${varname:+word}
1

上面语法的含义是,如果变量名存在且不为空,则返回 word,否则返回空值。它的目的是测试变量是否存在,比如 ${count:+1} 表示变量 count 存在时返回 1(表示 true),否则返回空值。

${varname:?message}
1

上面语法的含义是,如果变量 varname 存在且不为空,则返回它的值,否则打印出 varname: message,并中断脚本的执行。如果省略了 message,则输出默认的信息“parameter null or not set.”。它的目的是防止变量未定义,比如 ${count:?"undefined!"} 表示变量 count 未定义时就中断执行,抛出错误,返回给定的报错信息 undefined!

上面四种语法如果用在脚本中,变量名的部分可以用到数字 19,表示脚本的参数。

filename=${1:?"filename missing."}
1

上面代码出现在脚本中,1 表示脚本的第一个参数。如果该参数不存在,就退出脚本并报错。

# declare 命令

declare 命令可以声明一些特殊类型的变量,为变量设置一些限制,比如声明只读类型的变量和整数类型的变量。

它的语法形式如下。

declare OPTION VARIABLE=value
1

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

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

declare 命令如果用在函数中,声明的变量只在函数内部有效,等同于 local 命令。

不带任何参数时,declare 命令输出当前环境的所有变量,包括函数在内,等同于不带有任何参数的 set 命令。

declare
1

(1) -i 参数

-i 参数声明整数变量以后,可以直接进行数学运算。

$ declare -i val1=12 val2=5
$ declare -i result
$ result=val1*val2
$ echo $result
60
1
2
3
4
5

上面例子中,如果变量 result 不声明为整数,val1*val2 会被当作字面量,不会进行整数运算。另外,val1val2 其实不需要声明为整数,因为只要 result 声明为整数,它的赋值就会自动解释为整数运算。

注意,一个变量声明为整数以后,依然可以被改写为字符串。

$ declare -i var=12
$ var=foo
$ echo $var
0
1
2
3
4

上面例子中,变量 var 声明为整数,覆盖以后,Bash 不会报错,但会赋以不确定的值,上面的例子中可能输出 0,也可能输出的是 3。

(2) -x 参数

-x 参数等同于 export 命令,可以输出一个变量为子 Shell 的环境变量。

$ declare -x foo
# 等同于
$ export foo
1
2
3

(3) -r 参数

-r 参数可以声明只读变量,无法改变变量值,也不能 unset 变量。

$ declare -r bar=1

$ bar=2
bash: bar: 只读变量
$ echo $?
1

$ unset bar
bash: bar: 只读变量
$ echo $?
1
1
2
3
4
5
6
7
8
9
10
11

上面例子中,后两个赋值语句都会报错,命令执行失败。

(4) -u 参数

-u 参数声明变量为大写字母,可以自动把变量值转成大写字母。

$ declare -u foo
$ foo=upper
$ echo $foo
UPPER
1
2
3
4

(5) -l 参数

-l 参数声明变量为小写字母,可以自动把变量值转成小写字母。

$ declare -l bar
$ bar=LOWER
$ echo $bar
lower
1
2
3
4

(6) -p 参数

-p 参数输出变量信息。

$ foo=hello
$ declare -p foo
declare -- foo="hello"
$ declare -p bar
bar: 未找到
1
2
3
4
5

上面例子中,declare -p 可以输出已定义变量的值,对于未定义的变量,会提示找不到。

如果不提供变量名,declare -p 输出所有变量的信息。

declare -p
1

(7) -f 参数

-f 参数输出当前环境的所有函数,包括它的定义。

declare -f
1

(8) -F 参数

-F 参数输出当前环境的所有函数名,不包含函数定义。

declare -F
1

# readonly 命令

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

$ readonly foo=1
$ foo=2
bash: foo: 只读变量
$ echo $?
1
1
2
3
4
5

上面例子中,更改只读变量 foo 会报错,命令执行失败。

readonly 命令有三个参数。

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

# let 命令

let 命令声明变量时,可以直接执行算术表达式。

$ let foo=1+2
$ echo $foo
3
1
2
3

上面例子中,let 命令可以直接计算 1 + 2

let 命令的参数表达式如果包含空格,就需要使用引号。

let "foo = 1 + 2"
1

let 可以同时对多个变量赋值,赋值表达式之间使用空格分隔。

$ let "v1 = 1" "v2 = v1++"
$ echo $v1,$v2
2,1
1
2
3

上面例子中,let 声明了两个变量 v1v2,其中 v2 等于 v1++,表示先返回 v1 的值,然后 v1 自增。

这种语法支持的运算符,参考《Bash 的算术运算》一章。