1 变量
上一章节中,我们学习了脚本的基本用法与语法,而本章将更进一步,学习更多关于脚本相关的常见知识点。
而首先我们要学习的就是前面经常用到、但一直没讲解的变量,也就是使用$
符号作为前缀标识的单词。
Bash中的变量分成环境变量和自定义变量两类。
环境变量是 Bash 环境自带的变量,进入 Shell 时已经定义好了,可以直接使用。它们通常是系统定义好的,也可以由用户从父 Shell 传入子 Shell。
env
命令或printenv
命令,可以显示当前所有的环境变量。
env
# 或者
printenv
下面是一些常见的环境变量。
HOME
:用户的主目录。HOST
:当前主机的名称。LANG
:字符集以及语言编码,比如zh_CN.UTF-8
。PATH
:由冒号分开的目录列表,当输入可执行程序名后,会搜索这个目录列表。PS1
:Shell 提示符。PS2
: 输入多行命令时,次要的 Shell 提示符。SHELL
:Shell 的名字。TERM
:终端类型名,即终端仿真器所用的协议。UID
:当前用户的 ID 编号。USER
:当前用户的用户名。
很多环境变量很少发生变化,而且是只读的,可以视为常量。由于它们的变量名全部都是大写,所以传统上,如果用户要自己定义一个常量,也会使用全部大写的变量名。
注意Bash 变量名区分大小写,HOME
和home
是两个不同的变量。
查看单个环境变量的值,可以使用printenv
命令或echo
命令。
printenv PATH
# 或者
echo $PATH
注意,printenv
命令后面的变量名,不用加前缀$
。
自定义变量是用户在当前 Shell 里面自己定义的变量,仅在当前 Shell 可用。一旦退出当前 Shell,该变量就不存在了。
set
命令可以显示所有变量(包括环境变量和自定义变量),以及所有的 Bash 函数。
set
1.1 创建变量
用户创建变量的时候,变量名必须遵守下面的规则。
- 字母、数字和下划线字符组成。
- 第一个字符必须是一个字母或一个下划线,不能是数字。
- 不允许出现空格和标点符号。
变量声明的语法如下。
variable=value
上面命令中,等号左边是变量名,右边是变量。注意,等号两边不能有空格。
如果变量的值包含空格,则必须将值放在引号中。
myvar="hello world"
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)) # 变量值可以是数学运算的结果
变量可以重复赋值,后面的赋值会覆盖前面的赋值。
foo=1
foo=2
echo $foo
上面例子中,变量foo
的第二次赋值会覆盖第一次赋值,输出的结果将为2。
如果同一行定义多个变量,必须使用分号(;
)分隔。
foo=1;bar=2
上面例子中,同一行定义了foo
和bar
两个变量。
1.2 读取变量
读取变量的时候,直接在变量名前加上$
就可以了。
foo=bar
echo $foo
每当 Shell 看到以$
开头的单词时,就会尝试读取这个变量名对应的值,如果变量不存在,Bash 不会报错,而会输出空字符。
由于$
在 Bash 中有特殊含义,把它当作美元符号使用时,一定要非常小心,
echo The total is $100.00
上面命令的原意是输入$100
,但是 Bash 将$1
解释成了变量,该变量为空,因此输入就变成了00.00
。
所以,如果要使用$
的原义,需要在$
前面放上反斜杠,进行转义。
echo The total is \$100.00
读取变量的时候,变量名也可以使用花括号{}
包围,比如$a
也可以写成${a}
。这种写法可以用于变量名与其他字符连用的情况。
a=foo
echo ${a}_file
此时输出的结果为:
foo_file
事实上,读取变量的语法$foo
,可以看作是${foo}
的简写形式。
如果变量的值本身也是变量,可以使用${!varname}
的语法,读取最终的值。
myvar=USER
echo ${!myvar}
上面的例子中,变量myvar
的值是USER
,${!myvar}
的写法将其展开成最终的值,也就是变量USER
的值。
如果变量值包含连续空格(或制表符和换行符),最好放在双引号里面读取。
a="1 2 3"
echo $a
echo "$a"
上面示例中,变量a
的值包含两个连续空格。如果直接读取,Shell 会将连续空格合并成一个。只有放在双引号里面读取,才能保持原来的格式:
1.3 删除变量
unset
命令用来删除一个变量。
unset NAME
这个命令不是很有用。因为不存在的 Bash 变量一律等于空字符串,所以即使unset
命令删除了变量,还是可以读取这个变量,值为空字符串。
所以,删除一个变量,也可以将这个变量设成空字符串。
foo=''
foo=
上面两种写法,都是删除了变量foo
。由于不存在的值默认为空字符串,所以后一种写法可以在等号右边不写任何值。
1.4 导出变量
用户创建的变量仅可用于当前 Shell,子 Shell 默认读取不到父 Shell 定义的变量。为了把变量传递给子 Shell,需要使用export
命令。这样导出的变量,对于子 Shell 来说就是环境变量。
export
命令用来向子 Shell 导出变量。
NAME=foo
export NAME
上面命令输出了变量NAME
。变量的赋值和输出也可以在一个步骤中完成。
export NAME=value
上面命令执行后,当前 Shell 及随后新建的子 Shell,都可以读取变量$NAME
。
子 Shell 如果修改继承的变量,不会影响父 Shell。
# 输出变量 $foo
export foo=bar
# 新建子 Shell
bash
# 读取 $foo
echo $foo
# 修改继承的变量
foo=baz
# 退出子 Shell
exit
# 读取 $foo
echo $foo
上面例子中,子 Shell 修改了继承的变量$foo
,但对父 Shell 没有影响:
注意当输入bash命令时,虽然看上去没有发生任何变化,但实际上已经进入了一个新的子shell中,如果需要退出,则执行exit命令。
1.5 特殊变量
Bash 提供一些特殊变量,这些变量的值由 Shell 提供,用户不能进行赋值。
比如前文使用过的$?
为上一个命令的退出码,用来判断上一个命令是否执行成功。返回值是0
,表示上一个命令执行成功;如果不是零,表示上一个命令执行失败。
ls file.txt
echo $?
上面例子中,ls
命令查看一个不存在的文件,导致报错。$?
的输出结果为1,表示上一个命令执行失败。
$$
为当前 Shell 的进程 ID。
echo $$
这个特殊变量可以用来命名临时文件。
LOGFILE=/tmp/output_log.$$
$_
为上一个命令的最后一个参数。
grep dictionary /usr/share/dict/words
echo $_
执行效果:
$!
为最近一个后台执行的异步命令的进程 ID。
firefox &
echo $!
上面例子中,firefox
是后台运行的命令,$!
返回该命令的进程 ID。
$0
为当前 Shell 的名称(在命令行直接执行时)或者脚本名(在脚本中执行时)。
echo $0
上面例子中,$0
返回当前运行的是 Bash:
$-
为当前 Shell 的启动参数。
echo $-
$#
表示脚本的参数数量,$@
表示脚本的参数值。
1.6 默认值
Bash 提供四个特殊语法,跟变量的默认值有关,目的是保证变量不为空。
${varname:-word}
上面语法的含义是,如果变量varname
存在且不为空,则返回它的值,否则返回word
。它的目的是返回一个默认值,比如${count:-0}
表示变量count
不存在时返回0
。
${varname:=word}
上面语法的含义是,如果变量varname
存在且不为空,则返回它的值,否则将它设为word
,并且返回word
。它的目的是设置变量的默认值,比如${count:=0}
表示变量count
不存在时返回0
,且将count
设为0
。
${varname:+word}
上面语法的含义是,如果变量名存在且不为空,则返回word
,否则返回空值。它的目的是测试变量是否存在,比如${count:+1}
表示变量count
存在时返回1
(表示true
),否则返回空值。
${varname:?message}
上面语法的含义是,如果变量varname
存在且不为空,则返回它的值,否则打印出varname: message
,并中断脚本的执行。如果省略了message
,则输出默认的信息“parameter null or not set.”。
它的目的是防止变量未定义,比如${count:?"undefined!"}
表示变量count
未定义时就中断执行,抛出错误,返回给定的报错信息undefined!
。
上面四种语法如果用在脚本中,变量名的部分可以用数字1
到9
,表示脚本的参数。
filename=${1:?"filename missing."}
上面代码出现在脚本中,1
表示脚本的第一个参数。如果该参数不存在,就退出脚本并报错。
1.7 declare 命令
declare
命令可以声明一些特殊类型的变量,为变量设置一些限制,比如声明只读类型的变量和整数类型的变量。
它的语法形式如下。
declare OPTION VARIABLE=value
declare
命令的主要参数(OPTION)如下。
-a
:声明数组变量。-f
:输出所有函数定义。-F
:输出所有函数名。-i
:声明整数变量。-l
:声明变量为小写字母。-p
:查看变量信息。-r
:声明只读变量。-u
:声明变量为大写字母。-x
:该变量输出为环境变量。
declare
命令如果用在函数中,声明的变量只在函数内部有效,等同于local
命令。
不带任何参数时,declare
命令输出当前环境的所有变量,包括函数在内,等同于不带有任何参数的set
命令。
declare
-i
参数声明整数变量以后,可以直接进行数学运算。
declare -i val1=12 val2=5
declare -i result
result=val1*val2
echo $result
上面例子中,如果变量result
不声明为整数,val1*val2
会被当作字面量,不会进行整数运算。另外,val1
和val2
其实不需要声明为整数,因为只要result
声明为整数,它的赋值就会自动解释为整数运算。
注意,一个变量声明为整数以后,依然可以被改写为字符串。
declare -i var=12
var=foo
echo $var
上面例子中,变量var
声明为整数,覆盖以后,Bash 不会报错,但会赋以不确定的值,上面的例子中可能输出0,也可能输出的是3:
-x
参数等同于export
命令,可以输出一个变量为子 Shell 的环境变量。
declare -x foo
# 等同于
export foo
-r
参数可以声明只读变量,无法改变变量值,也不能unset
变量。
declare -r bar=1
# 均报错,无法修改
bar=2
unset bar
上面例子中,后两个赋值语句都会报错,命令执行失败。
-u
参数声明变量为大写字母,可以自动把变量值转成大写字母。
declare -u foo
foo=upper
echo $foo
-l
参数声明变量为小写字母,可以自动把变量值转成小写字母。
declare -l bar
bar=LOWER
echo $bar
-p
参数输出变量信息。
foo=hello
declare -p foo