Shell 之变量

发布于 2023-05-11 21:54:39 字数 6979 浏览 62 评论 0

配图源自 Freepik

在 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 内置的 envprintenv 命令可以查看所有的全局变量。但是,查看单个全局变量的值,echoprintenv 上稍有不同:

$ 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"

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

粉红×色少女

暂无简介

0 文章
0 评论
23 人气
更多

推荐作者

亽野灬性zι浪

文章 0 评论 0

少年亿悲伤

文章 0 评论 0

南七夏

文章 0 评论 0

qq_EJoXxu

文章 0 评论 0

17780639550

文章 0 评论 0

萌逼全场

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文