Bash 变量
Bash 变量
Bash 没有数据类型的概念,所有的变量值都是字符串。
bash 中还有数组,但是数组不是独立的数据类型。
我们在后面学习《Bash 函数》的时候就会发现,无论是往方法里传参还是获取方法返回值,都无法以数组类型实现。
简介
Bash 变量分成环境变量和自定义变量两类。
环境变量
环境变量是 Bash 环境自带的变量,进入 Shell 时已经定义好了,可以直接使用。它们通常是系统定义好的,也可以由用户从父 Shell 传入子 Shell。
当我们在 shell 脚本中通过
.
或者source
调用另一个脚本的时候,在父脚本中定义的变量,在子脚本中是可以直接使用的
env
命令或printenv
命令,可以显示所有环境变量。
《Bash 脚本》的
env
小节中对此命令进行了深入解析
|
|
环境变量太多,就不一一列举了,我本地的环境是 Centos 7:
HOME
:用户的主目录。HOST
:当前主机的名称。LANG
:字符集以及语言编码,比如zh_CN.UTF-8
、en_US.UTF-8
PATH
:由冒号分开的目录列表,当输入可执行程序名后,会搜索这个目录列表。PWD
:当前工作目录。OLDPWD
:上一个工作目录。SHELL
:Shell 的名字。TERM
:终端类型名,即终端仿真器所用的协议。USER
:当前用户的用户名。
很多环境变量很少发生变化,而且是只读的,可以视为常量。由于它们的变量名全部都是大写,所以传统上,如果用户要自己定义一个常量,也会使用全部大写的变量名。
注意,Bash 变量名区分大小写,HOME
和home
是两个不同的变量。
查看单个环境变量的值,可以使用printenv
命令或echo
命令。注意,printenv
命令后面的变量名,不用加前缀$
。
|
|
自定义变量
自定义变量是用户在当前 Shell 里面自己定义的变量,仅在当前 Shell 可用。一旦退出当前 Shell,该变量就不存在了。
set
命令可以显示所有变量(包括环境变量和自定义变量),以及所有的 Bash 函数。
|
|
然后我们能发现很多在env
命令中没有出现的变量
BASHOPTS
:当前 Shell 的参数,可以用shopt
命令修改。IFS
:词与词之间的分隔符,默认为空格。PS1
:Shell 提示符。PS2
:输入多行命令时,次要的 Shell 提示符。SHELLOPTS
:启动当前 Shell 的set
命令的参数,参见《set 命令和 shopt 命令》。
当然还有我们手动在 shell 中创建的变量。
创建变量
用户创建变量的时候,变量名必须遵守下面的规则。
- 字母、数字和下划线字符组成。
- 第一个字符必须是一个字母或一个下划线,不能是数字。
- 不允许出现空格和标点符号。
变量声明的语法如下。
|
|
上面命令中,等号左边是变量名,右边是变量。注意,等号两边不能有空格。因为 Bash 会根据空格来分割命令参数,如果有空格的话,Bash 会把variable
理解为一个命令,把=
和value
理解为传递给它的参数。
如果变量的值确实包含空格,则必须将值放在引号中。
|
|
在《转义和引号》中,我们研究过单引号字符串和双引号字符串,他们都可以赋值给变量
除此之外,我们经常通过子命令拓展来将一个命令的结果赋予一个变量
关于子命令拓展请看《Bash 的模式拓展》
|
|
或者将算数拓展的结果赋予一个变量
|
|
变量可以重复赋值,后面的赋值会覆盖前面的赋值。
简单实践如下:
|
|
输出:
|
|
读取变量
读取变量的时候,直接在变量名前加上$
就可以了。
|
|
每当 Shell 看到以$
开头的单词时,就会尝试读取这个变量名对应的值。
实际上这就是变量名拓展,具体请看《Bash 的模式拓展》
如果变量不存在,Bash 不会报错,而会输出空字符。
$
是特殊字符,无法作为普通字符直接输出,如果想要输出$
符号本身,需要在前面加上反斜杠进行转义,echo \$
,具体请查看《转义和引号》
读取变量的时候,变量名也可以使用花括号{}
包围,比如$a
也可以写成${a}
。这种写法可以用于变量名与其他字符连用的情况。
|
|
事实上,读取变量的语法$foo
,可以看作是${foo}
的简写形式。
如果变量的值本身也是变量,可以使用${!varname}
的语法,读取最终的值。注意,这个只能多查找一次,如下图示例中,${!name_wrapper_wrapper}
返回name
,而不是xiahsuo
|
|
如果变量的值包含连续空格(或制表符和换行符),最好将$val
放在双引号里面读取,例如echo "$val"
。
双引号可以保留变量格式,在《转义和引号》中学习过
|
|
删除变量
unset
命令用来删除一个变量。
也可以用来删除一个函数。例如
unset -f functionName
|
|
这个命令不是很有用。因为不存在的 Bash 变量在查看的时候会输出空字符串,所以即使unset
命令删除了变量,还是可以读取这个变量,值为空字符串。
所以,删除一个变量,也可以将这个变量设成空字符串。效果是一样的,😂
|
|
上面两种写法,都是删除了变量foo
。由于不存在的值默认为空字符串,所以后一种写法可以在等号右边不写任何值。一般选第二种,因为简短。
因此,我们可以通过以下脚本判断对象是否存在:
|
|
在父子 shell 中传递变量
什么是子 shell?什么时候产生子 shell?
请查看《Bash 子 shell》
跨进程(shell)中传递变量
用户创建的变量仅可用于当前 Shell,子 Shell 默认读取不到父 Shell 定义的变量。为了把变量传递给子 Shell,需要使用export
命令。这样输出的变量,对于子 Shell 来说就是环境变量。因此,export
命令修饰的变量,变量名最好全大写。表明这是全局变量。
export
命令用来向子 Shell 传递变量,因为export
声明的是环境变量,不过是临时的环境变量,在当前 Shell 会话中,所有 Shell 实例(包括当前 shell 进程和所有的子 shell 进程)都可以访问由 export 声明的临时环境变量。因为当前 Shell 会话中的所有 Shell 进程,都是当前 Shell 会话的子进程,所以可以与父进程一同访问环境变量。但在关闭当前 shell 会话后 export 声明的环境变量就会失效,比如退出 SSH 登录。
在 shell 脚本中调用另一个 shell 脚本的三种方式
.
和source
:这两种方式都不需要考虑变量传递的问题,因为他们没有创建子 shell,而是将指定的脚本内容拷贝至当前的脚本中,由一个 Shell 进程来执行。在 shell 脚本中,我们最常用的就是这种方式。
bash
或者sh
:这种方式实际上就是新开了一个 shell 进程来执行另一个脚本,这就需要考虑父 shell 到子 shell 的变量传递问题,因此需要使用export
来传递变量,
export
命令可以声明已经存在的变量,同时变量的赋值和export
输出也可以在一个步骤中完成。
|
|
上面命令执行后,当前 Shell 及随后新建的子 Shell,都可以读取变量$NAME
。而且子 Shell 如果修改继承的变量,不会影响父 Shell。
简单实践如下:
ts.sh
|
|
声明变量并测试:
|
|
可以看到,在执行脚本的时候,脚本中的$NAME
变量已经更新了,但是父 shell 中的$NAME
值没有变。
特殊变量
Bash 提供一些特殊变量。这些变量的值由 Shell 提供,用户不能进行赋值。
$?
$?
为上一个命令的退出码,可用来判断上一个命令是否执行成功。返回值是0
,表示上一个命令执行成功;如果不是零,表示上一个命令执行失败。
$?
可以获取上一个脚本,上一个方法,上一个命令的退出码
|
|
关于
$?
的具体使用,请看《Bash 脚本》的命令 / 脚本执行结果
小节
$$
$$
为当前 Shell 的进程 ID。
|
|
经常用此变量作为后缀来命名临时文件。比如日志文件。
|
|
大部分人都会直接通过$$
来判断是否进入子 shell,其实这样是不准确的,实际上,除了直接执行 bash 命令和 shell 脚本这两种子 shell,其他进入子 shell 的情况都会继承父 shell 中$$
变量的值。
关于什么时候会创建子 shell,请看《Bash 子 shell》
$_
$_
为上一个命令的最后一个参数。
|
|
$!
$!
为最近一个后台执行的异步命令的进程 ID。
|
|
在命令的最后添加&
就是将其放到后台执行。
$0
$0
为当前 Shell 的名称(在命令行直接执行时)或者脚本名(在脚本中执行时)。
|
|
$-
$-
为当前 Shell 的启动参数。
|
|
$@
和$#
$#
表示脚本的参数数量,$@
表示脚本的参数值,具体使用请看《Bash 脚本》
检查变量的值是否为空
Bash 提供四个特殊语法,跟变量的值有关,在变量的值为空或者不为空的时候执行不同的动作。
-
${varname:-word}
:如果变量varname
存在且不为空,则返回它的值,否则返回word
。它的目的是返回一个默认值,比如echo ${count:-0}
表示变量count
不存在时返回0
。 -
${varname:=word}
:如果变量varname
存在且不为空,则返回它的值,否则将它设为word
,并且返回word
。它的目的是设置变量的默认值,比如echo ${count:=0}
表示变量count
不存在时返回0
,且将count
设为0
。 -
${varname:+word}
:如果varname
存在且不为空,则返回word
,否则返回空值。它的目的是测试变量是否存在,比如echo ${count:+1}
表示变量count
存在时返回1
(表示true
),否则返回空值。 -
${varname:?message}
:如果变量varname
存在且不为空,则返回它的值,否则打印出varname: message
,并中断脚本的执行。如果省略了message
,则输出默认的信息“parameter null or not set.”。它的目的是防止变量未定义,这也是我们使用最多的场景,比如${count:?"undefined!"}
表示变量count
未定义时就中断执行,抛出错误,返回给定的报错信息undefined!
。
上面四种语法如果用在脚本中,变量名的部分可以用数字1
到9
,表示脚本的参数。
|
|
上面代码出现在脚本中,1
表示脚本的第一个参数。如果该参数不存在,就退出脚本并报错。
简单实践一下:
主要实践一下${varname:?message}
,我们一般也就是用这个来在方法中或者子脚本中进行参数的判断,不存在就退出执行。
ts.sh
|
|
执行:
|
|
会发现脚本中途退出了,在脚本中设置了 name 的值之后
|
|
脚本正常执行完了
|
|
修改ts.sh
,检查参数
|
|
可以看到,只有参数填全了,才能正常执行脚本
|
|
declare 命令
普通变量在使用之前不需要专门声明,直接写varname=value
就可以了,declare
命令主要用于声明一些特殊类型的变量,为变量设置一些限制,比如声明只读类型的变量和整数类型的变量。
它的语法形式如下。
|
|
declare
命令的主要参数(OPTION)如下。
-a
:声明数组变量。-A
:声明关联数组。-f
:输出所有函数定义。-F
:输出所有函数名。-i
:声明整数变量。-l
:声明变量为小写字母。-p
:查看变量信息。-r
:声明只读变量。-u
:声明变量为大写字母。-x
:该变量输出为环境变量。
declare
命令如果用在函数中,声明的变量只在函数内部有效,等同于local
命令。
Bash 函数体内直接声明的变量,属于全局变量,整个脚本都可以读取。这一点需要特别小心。
local
命令声明的变量,只在函数体内有效,函数体外没有定义更多细节,请看《Bash 函数》
不带任何参数时,declare
命令输出当前环境的所有变量,包括函数在内,等同于不带有任何参数的set
命令。
-a
参数
-a
参数用于命令声明一个数组
|
|
关于数组的更多知识,请看《Bash 数组》
-A
参数
-A
选项就是用于声明一个关联数组。
什么是关联数组?关联数组可以使用任意的字符串、或者整数作为下标来访问数组元素。有点像Map
这种数据结构。
|
|
关于数组的更多知识,请看《Bash 数组》
-i
参数
-i
参数声明整数变量以后,则会将赋予此变量的表达式的结果理解为数字,如果表达式为数学运算,则会进行数学运算算出一个结果。如果表达式中包含的表达式中包含字符串,则会将字符串理解为数字 0,参与数学计算。
关于整数的运算,请看《Bash 的算术运算》
|
|
-x
参数
-x
参数等同于export
命令,可以输出一个变量为子 Shell 的环境变量。
|
|
-r
参数
-r
参数可以声明只读变量,无法改变变量值,也不能unset
变量。
效果跟readonly
一样
|
|
-u
参数
-u
参数声明变量的值全都是大写字母,可以自动把变量值转成大写字母。
|
|
-l
参数
`-l``参数声明变量的值全都是小写字母,可以自动把变量值转成小写字母。
|
|
-p
参数
-p
参数输出变量信息。
declare -p
可以输出已定义变量的值,对于未定义的变量,会提示找不到。
如果不提供变量名,declare -p
输出所有变量的信息,因为declare
命令默认就是输出所有的变量和方法。
|
|
-f
参数和-F
参数
-f
参数输出当前环境的所有函数,包括它的定义。-F
参数输出当前环境的所有函数名,不包含函数定义。
|
|
|
|
readonly 命令
readonly
命令等同于declare -r
,用来声明只读变量,不能改变变量值,也不能unset
变量。
readonly
命令有三个参数。
-f
:声明的变量为函数名。-p
:打印出所有的只读变量。- 这个很好用
-a
:声明的变量为数组。
简单实践如下
|
|
输出:
|
|
你也可以把readonly
和变量的初始化写在一行
|
|
let 命令
let
命令声明变量时,可以直接执行算术表达式。let
命令的参数表达式如果包含空格,就需要使用引号。
|
|
let
可以同时对多个变量赋值,赋值表达式之间使用空格分隔。
|
|
上面例子中,let
声明了两个变量v1
和v2
,其中v2
等于v1++
,表示先返回v1
的值,然后v1
自增。
这种语法支持的运算符,参考《Bash 的算术运算》一章。