Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Shell 之变量(二) #40

Open
toFrankie opened this issue Feb 25, 2023 · 0 comments
Open

Shell 之变量(二) #40

toFrankie opened this issue Feb 25, 2023 · 0 comments
Labels
2022 2022 年撰写 Linux 与 Linux 相关的文章 Shell 与 Shell、Bash、Zsh、Sh 等相关的文章

Comments

@toFrankie
Copy link
Owner

toFrankie commented Feb 25, 2023

配图源自 Freepik

上一篇: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 内置的 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"

@toFrankie toFrankie added Linux 与 Linux 相关的文章 Shell 与 Shell、Bash、Zsh、Sh 等相关的文章 labels Feb 25, 2023
@toFrankie toFrankie added the 2022 2022 年撰写 label Apr 26, 2023
This was referenced Aug 2, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
2022 2022 年撰写 Linux 与 Linux 相关的文章 Shell 与 Shell、Bash、Zsh、Sh 等相关的文章
Projects
None yet
Development

No branches or pull requests

1 participant