上一篇:Shell 之初识
下一篇:Shell 之数据类型
在 Shell 中,变量分为「环境变量」和「自定义变量」,也包括一些特殊变量(如 $@
、$0
、$$
等)。(永久性)环境变量在进入 Shell 时已经定义好了, 可以直接使用它们。
一、读取变量
当一个变量声明之后,在其作用域访问内,便可被访问。变量读取有两种方式:
$变量名
${变量名}
其中 $foo
和 ${foo}
两种写法效果是一样的。前者可以理解为后者的简写形式。
$ echo $USER
frankie
$ echo ${USER}
frankie
对于 ${变量名}
可用于变量与其他字符连用的情况。比如:
$ a='foo'
# 以下读取的名为「a_file」的变量,由于不存在,因此输出空字符。
$ echo $a_file
$ echo ${a}_file
foo_file
在其他高级语言中,如果引用了一个不存在的变量,可能会抛出错误。比如在 JavaScript 中会抛出 Reference Error。但是,在 Shell 中,如果引用的变量不存在,它不会报错,而是输出「空字符」。
$ echo $unknow_var
二、环境变量
大多数环境变量,都是「只读」的,可视为「常量」。常见环境变量有:
-
USER
- 当前登录用户 -
HOME
- 当前用户目录 -
PATH
- 系统查找指令时的检索目录 -
PWD
- 当前工作目录 -
OLDPWD
- 上一个工作目录 -
SHELL
- 当前系统默认 Shell - 还有很多,不一一列举了...
全局变量的读取同上。
同时,Shell 内置的 env
和 printenv
命令可以查看所有的全局变量。但是,查看单个全局变量的值,echo
和 printenv
上稍有不同:
$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
$ printenv PATH
/usr/local/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
👆 其中 printenv
命令后的变量名,是不用添加 $
前缀的。
参考主流的 Shell 编程风格指南:常量和任何导出到环境的(自定义)变量,其变量名应「大写」表示,用下划线「
_
」分隔,且声明在文件顶部。
内置的环境变量也是如此,推荐看 Google Shell Style Guide。
三、自定义变量
自定义变量,就是用户在当前 Shell 中定义的变量。
使用 set
命令可以查看当前 Shell 的变量(包括环境变量和自定义变量),以及所有 Shell 内置函数。
变量定义形式如下:
name=value
👆 等号左边为变量名,等号右边为变量值。注意,等号两边不能有空格。
对于变量名命名限制,大致上与其他高级语言类似。以下仅列举主流编程风格中推荐的用法:
- 只能使用英文字母、数字和下划线,且首字母不能以数字开头。
- 不能与内置变量重名。
- 中间不能有空格,且应使用下划线分割。
- 全局变量应大写表示,其余的应小写表示。
对于变量值,此处只说明以下几点:
- 若变量值不包含「空格」,引号是可以省略的,但不推荐。
- 变量值应根据实际情况选择用「单引号」或「双引号」包裹。尽管是可选的,但推荐用引号。
- 对于「单引号」:用于保留字符的字面含义,单引号内的各种特殊字符,都会变为普通字符,原样输出。
- 对于「双引号」:比单引号宽松,大部分特殊字符在双引号里面,都会失去特殊含义,变成普通字符。但是,三个特殊字符除外:美元符号(
$
)、反引号(`
)和反斜杠(\
)。这三个字符在双引号之中,依然有特殊含义,会被 Bash 自动扩展。
其他注意点,下文介绍「数据类型」时再作详细介绍。举个例子:
$ echo '$USER'
$USER
$ echo "$USER"
frankie
四、变量作用域
跟其他高级语言中一样,Shell 的变量也是有作用域(Scope)的,主要分为三种:
- 局部变量:其作用域为函数体内部。
- 全局变量:其作用域为当前 Shell 进程。
- 环境变量:其作用域为当前 Shell 进程及其子进程。
局部变量使用 local
命令进行声明,比如 local foo="bar"
。
function fn() {
local foo="bar"
}
fn
echo $foo # 输出空字符
👆 echo $foo
输出空字符,由于变量 foo
为局部变量,只能在函数 fn
内使用,因此函数外部无法找到变量而输出空字符。
function fn() {
foo="bar"
}
fn
echo $foo # 输出 bar
👆 由于在 Shell 中定义的变量默认为全局变量,因此 echo $foo
输出 bar
。
$ foo="bar"
$ echo $foo # bar
$ echo $$ # 16531 当前进程 ID
$ bash # 创建并进入子进程
$ echo $$ # 16846 子进程 ID
$ echo $foo # 输出空字符
$ foo="baz" # 在子进程设置变量
$ echo $foo # 输出 baz
$ exit # 退出子进程,然后返回父进程中
$ echo $$ # 16531 当前进程 ID
$ echo $foo # bar
👆 由于全局变量仅在当前进程中有效,因此进入子进程后,找不到变量 foo
,因此子进程中输出空字符。同时在子进程中设置的全局变量,不会影响到父进程,因此退出子进程返回到父进程后,父进程的 foo
变量并未发生改变。
环境变量,根据持久性可以划分为:「永久性环境变量」和「临时性环境变量」。
- 永久性环境变量:即在 Shell 配置文件(比如
~/.zshrc
、~/.bash_profile
等)中的声明的变量,包括内置的环境变量,进入任意一个 Shell 进程都可被引用。因为每启动一个进程之前 Shell 都会去执行相应的配置文件。- 临时性环境变量:在全局变量的基础上,使用
export
命令导出,使得当前进程及其子孙进程都可引用。但是,其他 Shell 进程(包括当前进程的父进程)是不可引用的。当退出进程,便会被销毁。
临时性环境变量,只会向下传递,而不能向上传递,即「传子不传父」。
使用 export
命令,可以用来向 Shell 子进程输出变量。
FOO="bar"
export FOO
五、变量默认值
前面提到,在 Shell 中,如果读取了一个不存在的变量,它是不会报错的,而是会输出空字符。在 Shell 中,提供了四种特殊语法,与变量的默认值有关,可以保证读取到的结果不为空。
形式为:${变量名 + : + 操作符 + 值}
,注意实际使用是没有空格的。比如,${foo:-hello}
表示当变量 foo
存在时返回它的值,不存在则返回 hello
。其中 varname
为变量名,:
是固定的,-
为操作符(还有 =
、+
、?
),hello
为值。
有以下四种情况:
$ foo=${bar:-hello} # 相当于 JS 中的 foo = bar || 'hello'
$ foo=${bar:=hello} # 相当于 JS 中的 foo = bar || (bar = 'hello')
$ foo=${bar:+hello} # 相当于 JS 中的 foo = !bar ? 'hello' : ''
$ foo=${bar:?hello} # 相当于 JS 中的 foo = bar || (throw 'hello')
👆 以上四种形式,相同的是:当变量 bar
存在且不为空时,右侧输出结果为变量 bar
的值,因此变量 foo
的值等于变量 bar
的值。
区别在于:
${bar:-hello}
- 表示当变量bar
存在且不为空时,返回变量bar
的值,否则返回hello
。目的是为了返回一个默认值。${bar:=hello}
- 表示当变量bar
存在且不为空时,返回变量bar
的值,否则将变量bar
的值设为hello
并返回hello
。目的是变量的默认值。${bar:+hello}
- 表示当变量bar
存在且不为空时,返回hello
,否则返回空值。目的是为了判断一个变量是否存在。${bar:?hello}
- 表示当变量bar
存在且不为空时,返回变量bar
的值,否则输出错误信息bar: hello
,并中断脚本执行。目的是为了防止变量未定义。
六、特殊变量
Shell 提供了一些特殊变量,用户不能对其进行赋值,即只读。
$?
- 表示上一个命令的退出码。若上一个命令执行成功,则返回 0,因此,若返回值不为 0 ,则表示上一个命令执行失败。$$
- 表示当前 Shell 进程 ID。$_
- 表示上一个命令的最后一个参数。$!
- 表示最后一个后台执行的异步命令的进程 ID。$-
- 表示当前 Shell 的启动参数。$#
- 表示脚本或函数的参数数量。$@
- 表示脚本或函数的全部参数,参数之间使用空格隔开。$*
- 表示函数的全部参数,参数之间使用变量$IFS
值的第一个字符分割,默认为空格,可自定义。$0
- 表示当前 Shell 的名称(在命令直接执行时)或脚本名(在脚本中执行时)。$1
~$9
- 表示脚本或函数第一个到第九个参数,也可用${0}
表示。超过第 9 个,则用${10}
形式获取。
七、其他
unset
命令可以用来删除一个变量,基于 Shell 读取不存在的变量会得到空字符的特性,它相当于给变量设置为空字符串。
declare
命令可以声明一些特殊类型的变量。若在函数中使用 declare
声明的变量,仅函数内有效,相当于 local
命令。
declare OPTION variable=value
# 主要 OPTION 参数如下:
# -a: 声明数组变量
# -i: 声明整数变量
# -r: 声明只读变量
# ...
readonly
命令等同于 declare -r
,用来声明只读变量,不能改变变量值,也不能 unset
变量。
let
命令声明变量时,可以直接执行算术表达式。
$ let sum=1+2
$ echo sum
3
如果包含空格,则需要「引号」,比如 let "sum = 1 + 2"
。
网友评论