阅读 60

set -e、set -u作用及作用域、如何测试变量是否设置等

set -e、set -u作用及作用域、如何测试变量是否设置等

lowcoder-hing
2021-09-24

相关命令及参数介绍

# 简而言之,表示此命令后,当某命令返回值非0时,将出错。如果是非交互环境,将直接退出,不再执行后续命令
set -e
# 即set -e的反向操作,恢复bash shell的默认行为,命令失败后继续执行后续命令
set +e

# 即set -o nounset。
# 简而言之,表示此命令之后,当某命令使用了未定义变量或参数时(特殊参数“@”和“*”除外),将打印错误信息
# 如果是非交互环境(通常为脚本中),将直接退出,不再执行后续命令
set -u
# 即set -u的反向操作,恢复bash shell的默认行为,命令使用未定义变量或参数时,继续执行后续命令
set +u

根据man set中set章节的说明,这里补充说明一下。

set -e是指,如果失败的命令在管道、括号内的子shell命令或大括号括起的命令列表中,则直接以非零状态退出。
如果失败的命令是紧跟在while或until关键字之后的命令列表的一部分、if或elif保留字之后的测试的一部分、&&或||列表中的任何命令的一部分,则情况有所不同。除了此部分内的后续命令不执行,在此部分之外的命令则继续执行。
此部分内的命令:&&或||中,最后的命令、管道中的后续命令,此命令的!反转。
如果设置了捕获ERR,则在shell退出之前执行trap on ERR。
此选项分别应用于shell环境和每个子shell环境,并可能导致子shell在执行子shell中的所有命令之前退出。

关于作用域

生效的作用域为执行以上相关命令后,在本进程未碰到反向操作的命令之前【例如:set -u后未碰到set +u之前】。
如果退出此进程回到父进程则失去效果,而如果是子进程则继承了本进程的bash环境设置。

需要注意:
如果使用$(myfun)的方式来调用函数,需要清楚myfun其实是在子进程中执行的,即myfun中设置的set -u/set +u不影响外部的行为。

set -u中易错情形

set -u后,如果使用未定义的变量,或变量为空字符串、空数组,将打印错误(xx为相关变量名):

xx: unbound variable

一些需要留意的点:

数组问题

如果某数组没有元素,会报unbound variable,但是又可以使用获取数组长度的操作。例如以下操作:

arr=()
# echo ${arr[@]}将出错,不能执行
echo ${arr[@]}
# echo ${#arr[@]}不会出错,可以获取长度为0
echo ${#arr[@]}

函数传参问题

调用函数传递不固定数量的参数时,接收参数的函数内部需要注意变量数量问题。
例如外部调用可能传入1个参数,也可能传入2个参数,则在函数中,使用$2有可能直接报错。
应当先判断参数数量足够,再使用合适的变量。例如:

# 在外部调用只传递一个参数时,下面这样赋值将出错
function myfun() {
  local a="$1" b="2"
  echo $b
}

# 在外部调用只传递一个参数时,下面这样赋值不会出错
function myfun() {
  local a="$1" b
  [[ $# -ge 2 ]] && b="$2"
  echo $b
}

# 或者使用后面将会提到的${parameter:-word}语法,测试变量是否设置及空值
function myfun() {
  local a="$1" b
  b="${2:-}"
  echo $b
}

其他特殊问题

使用间接的一维关联数组的情况下,有一些需要留意的情况。
实际上,这种情况通常是业务需要2维数组,但是因为bash shell不支持,所以用两个数组来模拟实现。
此情况下,A数组提供B数组的数组名,用eval间接模拟取B数组的某一key对应的值。

此时会碰到很多麻烦,因为间接数组中是否存在需要取的key,还需要再判断一遍,否则会因未定义变量而出错。
较为合理的解决方法,似乎只有两种:

  1. 先遍历一遍间接数组b的所有key,是否存在想获取的key。而此时因为是间接数组,都要通过eval处理,相当麻烦
  2. 在进程中,临时关闭set -u的特性【调用set +u】,操作结束后再调用set -u。

其中方法2)最简便省事,此处因为version_get_val函数通常是以$(version_get_val "$ver" "$idx")的形式调用,即在子进程中执行,可以直接在内部调用set +u。而以下示例代码中,放到了eval中去调用,相当于在子作用域中调用。

set -u

# 外层数组
declare -A VERS_INFO=(
[develop]=DEV_INFO
[stable]=STB_INFO
)

# 内层的间接数组,实际需要取其中的key值,但是某些key可能是并未完整设置的
declare -A STB_INFO=(
[verFlag]="cn"
[path]=/data/stb
)
declare -A DEV_INFO=(
[path]=/data/dev
[isTest]="TRUE"
)

# 输出指定的参数,不存在时返回空字符串
function version_get_val() {
    local ver=$1 idx=$2
    # 如果不调用set +u,当想访问一个子数组不存在的key时,将出错。
    # 例如调用 version_get_val "stable" "isTest" 【stable版本并没有设置isTest参数】

    # set +u
    # eval ‘echo -n \${${VERS_INFO[$ver]}[$idx]}‘
    eval "set +u; echo -n \${${VERS_INFO[$ver]}[$idx]}"
}

测试变量的表达式${parameter:-word}等

实际上,需要测试变量是否设置、是否空值,还可以用${parameter:-word}、${parameter:+word}等几个表达式。

在这里,-、+、:-、:+是什么含义呢?

前两者-、+会测试这个变量是否设置。
后两者:-、:+会测试这个变量是否设置,以及是否空值。

(感觉这几个操作,有点其他编程语言中三元运算符的味道)

从man bash中查看“Parameter Expansion”一节,摘录了相关几个语法的说明简单翻译如下。

${parameter:-word}
# 使用默认值。
# 如果parameter未设置或空,则返回word。否则返回parameter。

${parameter:=word}
# 分配默认值。
# 如果parameter未设置或空,则将word赋值给parameter,并返回。否则返回parameter。

${parameter:?word}
# 如果未定义或空值,展示错误。
# 如果parameter未设置或空,将在标准错误设备打印word消息,此时如果非交互环境将退出。否则返回paramter。

${parameter:+word}
# 使用候选值。
# 如果parameter未设置或空,将返回空值。否则,返回word值(并不修改parameter的值)。

更多细节可以查看man bash的“Parameter Expansion”一节。

此次查看man bash的意外收获:
根据手册说明, $(cat file)可以用$(< file)来代替,效果相同但效率更高。
个人浅见,推测cat file需要读取再输出,而< file直接重定向,省去了缓冲区的拷贝转移【不负责任的推测】

原文:https://www.cnblogs.com/lowcoder-hing/p/15332809.html

文章分类
百科问答
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐