前言
当我们通过 Bash 执行 Script时,会创建一个子Shell进程进行执行。而且子Shell中的执行环境是一个独立的属于自己的环境,Bash 会给这个环境一些默认参数。
我们可以通过一些命令进行配置,改变执行环境中的一些参数,从而改变执行环境。即自定义执行环境。自定义执行环境与外层的父Shell的执行环境是两个互不影响的执行环境。也就说不必担心在子Shell中变更了执行环境会影响到父Shell的执行化境。
如何去改变呢,可以通过set、shopt
命令进行改变。shopt
命令在前面(Shell 学习(3)Bash 的模式扩展
)已经详细介绍过了,那么与set
命令的区别在哪呢?
-
set
命令从 Ksh 继承的,属于 POSIX 规范的一部分。 -
shopt
是 Bash 特有的。
这两个命令都可以去改变当前 Shell 的执行环境的。下面就主要介绍一下set
命令的参数。
set 命令
官方手册
我们介绍最常用的几个。
1、set -u
:读取不存在的变量时抛出错误信息,终止执行。而不是默认的进行忽略。
等价于:set -o nounset
#!/bin/bash
set -u
echo $a
echo "end line"
改配置前输出:
$ ./test.sh
end line
改配置后输出:
$ ./test.sh
./test.sh: line 4: a: unbound variable
2、set -x
:在命令的运行结果前,输出执行的命令。
关闭参数:set +x
等价于:set -o xtrace
#!/bin/bash
set -x
number=1
if [ $number = 1 ]; then
echo "Number is equal to 1."
else
echo "Number is not equal to 1."
fi
输出:
$ ./test.sh
+ number=1
+ '[' 1 = 1 ']'
+ echo 'Number is equal to 1.'
Number is equal to 1.
不仅仅是命令,赋值运算操作也会进行输出。
但是感觉每行输出前的+
不能直观的定位到行代码,该怎么改变呢?
在 Scirpt 顶部修改一下PS4
环境变量,更改为取LINENO
行号环境变量:
export PS4='${LINENO}: '
输出:
$ ./test.sh
+ PS4='${LINENO}: '
6: number=1
7: '[' 1 = 1 ']'
8: echo 'Number is equal to 1.'
Number is equal to 1.
这样输出信息就更加直观了。但是千万注意:PS4='${LINENO}: '
,取行号时是用的单引号。如果改为双引号,那么PS4
环境变量会被赋值为赋值行的行号。那么在后面输出的行号会都是一个值,即赋值行行号。
3、set -e
:产生错误时就终止执行(Script 的异常处理)。
关闭参数:set +e
等价于:set -o errexit
执行 Script 时,如果执行了一些退出码为非0的运行失败命令,Bash 默认会继续执行后面的内容,而不是终止 Script 的执行。
开发时我们期待的总是发生执行异常时退出,而不是继续执行。那么可能会通过下面的方式进行处理:
command || exit 1
通过逻辑或运算符去执行exit
命令,使程序终止。
如果我们还期望在终止执行前,在做一些其他的操作:
# 写法一
command || { echo "command failed"; exit 1; }
# 写法二
if ! command; then echo "command failed"; exit 1; fi
# 写法三
command
if [ "$?" -ne 0 ]; then echo "command failed"; exit 1; fi
这些写法,多少有些麻烦。可以通过set -e
,交给 Bash 检查并抛出异常信息后中断执行:
#!/bin/bash
set -e
nonexistentCommand
echo "end line"
改配置前输出:
$ ./test.sh
./test.sh: line 4: nonexistentCommand: command not found
end line
改配置后输出:
$ ./test.sh
./test.sh: line 5: nonexistentCommand: command not found
但是如果当我们即修改了配置,又做了容错处理,会导致无法中断 Script:
#!/bin/bash
set -e
nonexistentCommand || echo "error"
echo "end line"
输出:
$ ./test.sh
./test.sh: line 5: nonexistentCommand: command not found
error
end line
还有一种情况,希望在命令失败的情况下,脚本继续执行下去。这时可以暂时关闭set -e
,该命令执行结束后,再重新打开set -e
set +e
command1
command2
set -e
4、set -o pipefail
:解决管道命令中的特殊情况。
set -e
有一个例外情况:不适用管道命令。
#!/bin/bash
set -e
nonexistentCommand | echo "after error"
echo "end line"
输出:
$ ./test.sh
./test.sh: line 4: nonexistentCommand: command not found
after error
end line
依旧打印了最后一行的输出,这是因为,只要管道符的最后一个命令不执行失败,返回的结果总是成功。
解决方式:set -eo
#!/bin/bash
set -e
set -o pipefail
nonexistentCommand | echo "after error"
echo "end line"
输出:
$ ./test.sh
after error
./test.sh: line 5: nonexistentCommand: command not found
5、set -E
:修复set -e
引起的函数内的异常信息不会被trap
命令捕获的问题。
trap
命令先理解为捕获 Script 发生的错误即可,后面会对该命令进行说明。
#!/bin/bash
set -e
trap "echo ERR trap fired!" ERR
func()
{
# 执行一个不存在的命令
nonexistentCommand
}
func
输出:
$ ./test.sh
./test.sh: line 9: nonexistentCommand: command not found
非函数内引发的异常是可以被正常捕获的:
#!/bin/bash
set -e
trap "echo ERR trap fired!" ERR
nonexistentCommand
func
输出:
$ ./test.sh
./test.sh: line 6: nonexistentCommand: command not found
ERR trap fired!
6、其他参数
set命令还有一些其他参数。
set -n:等同于set -o noexec,不运行命令,只检查语法是否正确。
set -f:等同于set -o noglob,表示不对通配符进行文件名扩展。
set -v:等同于set -o verbose,表示打印 Shell 接收到的每一行输入。
set -o noclobber:防止使用重定向运算符>覆盖已经存在的文件。
上面的-f和-v参数,可以分别使用set +f、set +v关闭。
set 命令总结
上面重点介绍的set命令的几个参数,一般都放在一起使用:
# 写法一
set -Eeuxo pipefail
# 写法二
set -Eeux
set -o pipefail
这两种写法建议放在所有 Bash 脚本的头部。
另一种办法是在执行 Bash 脚本的时候,从命令行传入这些参数:
$ bash -euxo pipefail script.sh
根据个人习惯,也可以修改一下PS4
环境变量:export PS4='${LINENO}: '
。