美文网首页
Shell 之变量(二)

Shell 之变量(二)

作者: 越前君 | 来源:发表于2022-08-24 22:43 被阅读0次
    配图源自 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"

    相关文章

      网友评论

          本文标题:Shell 之变量(二)

          本文链接:https://www.haomeiwen.com/subject/acnogrtx.html