使用非官方的严格模式 (除非你喜欢debug)
对原作者表示尊重:(http://redsymbol.net/articles/unofficial-bash-strict-mode/)
核心是在脚本中采用这样的头部:
#! /bin/bash
set -euo pipefail
IFS=$'\n\t'
目的是让脚本变得更加健壮,让一下模糊的,不确定的,隐形的错误,显示出来,利于更好的控制
这个参数设定,但命令返回非0时退出脚本。意思是在脚本中对出现的错误进行零容忍。
但是有时候,可能在脚本中执行一些期望报错的命令,该怎么办呢?可以这样:
# "grep -c" reports the number of matching lines. If the number is 0,
# then grep's exit status is 1, but we don't care - we just want to
# know the number of matches, even if that number is zero.
# Under strict mode, the next line aborts with an error:
count=$(grep -c some-string some-file)
# But this one behaves more nicely:
count=$(grep -c some-string some-file || true)
echo "count: $count"
可以让这行命令不报错。
当然也可以临时性的去掉-e模式,
这样:
# We had started out this script with set -e . And then...
set +e
count=$(grep -c some-string some-file)
retval=$?
set -e
# grep's return code is 0 when one or more lines match;
# 1 if no lines match; and 2 on an error. This pattern
# lets us distinguish between them.
echo "return value: $retval"
echo "count: $count"
设定不能使用未声明的变量,否则会报错。
这个规则和C、C++、Java的设定是一样的。
这个参数设定后,会有坑当使用$1, 0 , 0, 0,#等参数时,脚本会退出,并报错,变量未声明的异常信息 解决办法使用:-
:
#!/bin/bash
set -u
name=${1:-}
if [[ -z "$name" ]]; then
echo "usage: $0 NAME"
exit 1
fi
echo "Hello, $name"
当$1为声明时,使用默认值,这里默认值为空。
更加完全的解释:
# Variable $foo has not been set. In strict mode,
# next line triggers an error.
bar=$foo
# ${VARNAME:-DEFAULT_VALUE} evals to DEFAULT_VALUE if VARNAME undefined.
# So here, $bar is set to "alpha":
bar=${foo:-alpha}
# Now we set foo explicitly:
foo="beta"
# ... and the default value is ignored. Here $bar is set to "beta":
bar=${foo:-alpha}
# To make the default an empty string, use ${VARNAME:-}
empty_string=${some_undefined_var:-}
还有一个坑就在于使用外部,或者系统定义的一些脚本的时候,会出现某某变量没有声明的情况。也很正常,当你的脚本时严格控制变量的时候,你不能控制其他的脚本,特别是一些系统脚本的变量也是使用-u的模式。比如:
set -u
source /path/to/venv/bin/activate
_OLD_VIRTUAL_PYTHONHOME: unbound variable
# This causes your strict-mode script to exit with an error.
这个时候我们将这个命令从-u模式下exclude出去:
set +u
source /path/to/venv/bin/activate
set -u
(Remember, set +u disables this variable strictness, and set -u enables it. A bit counterintuitive, so take care here until it’s second nature.)这里的+ 和 - 有点和平常的不一样,特别注意一下。
还有一个值得注意的是,在默认的模式下,我们可以在一些循环中直接使用未声明的变量,但是在严格模式中,这是不允许的。建议的解决方式是提前声明,比如:
someVar=""
# ...
# a bunch of lines of code that may or may not set someVar
# ...
if [[ -z "$someVar" ]]; then
# ...
在使用管道的时候,中间的命令即使出现错误,在默认模式下,也可以继续运行下去。设置了-e也没有效果,因为整条命令可以返回0,也就是没有报错。但是这和期望是不一样的,管道命令中的内部报错会影响到最终的结果。
% grep some-string /non/existent/file | sort
grep: /non/existent/file: No such file or directory
% echo $?
0
看到最后的输出是0
再看:
% set -o pipefail
% grep some-string /non/existent/file | sort
grep: /non/existent/file: No such file or directory
% echo $?
2
这是因为,当grep名没有找到文件后,他的标准错误输出是错误信息,标准输出是空。通过管道将标准输出给sort命令,传入一个空值给sort,对于sort来说是可以接受的,最后返回0。
加入-o pipefail来避免这种情况的影响。
显示的设置内部字段分隔符
来看例子:
#!/bin/bash
IFS=$' '
items="a b c"
for x in $items; do
echo "$x"
done
IFS=$'\n'
for y in $items; do
echo "$y"
done
打印的结果:
a
b
c
a b c
强烈建议使用这样的代码段,来释放你的资源。特别是当你使用一些昂贵资源的时候:
scratch=$(mktemp -d -t tmp.XXXXXXXXXX)
function finish {
rm -rf "$scratch"
}
trap finish EXIT
# Now your script can write files in the directory "$scratch".
# It will automatically be deleted on exit, whether that's due
# to an error, or normal completion.
关于trap
命令可以让我们的脚本在出现错的情况下也能 正确的做一些事情,比如释放资源。类似于finally要做的一些事。详细内容参见:(http://redsymbol.net/articles/bash-exit-traps/)
我们用这样的使用方式:
COND && COMMAND
当条件为true的时候,执行command。像if 一样。
# Prints a message only if $somefile exists.
[[ -f "$somefile" ]] && echo "Found file: $somefile"
当有多个任务进行短路运行时,第一个的错误会影响到后续的任务。但是潜在的问题是,当second_task失败的时候,third_task不会运行,但是会执行下一行的next_task
first_task && second_task && third_task
# And more lines of code following:
next_task
如果你想当second_task失败的时候,脚本直接退出,而不是继续执行下一行代码。那么这样写可以帮助你达到期望。
first_task && {
second_task
third_task
}
next_task
在严格的模式下,当脚本的最后一行是CONF && COMMAND
时,会出现错误,来看例子:
% cat short-circuit-last-line.sh
#!/bin/bash
set -euo pipefail
# omitting some lines of code...
# Prints a message only if $somefile exists.
# Note structure: COND && COMMAND
[[ -f "$somefile" ]] && echo "Found file: $somefile"
% ./short-circuit-last-line.sh
% echo $?
1
这是一个bug,当最后一行时其他的命令就没有问题:
% cat short-circuit-before-last-line.sh
#!/bin/bash
set -euo pipefail
# omitting some lines of code...
# Prints a message only if $somefile exists.
# Structure: COND && COMMAND
# (When we run this, $somefile will again not exist,
# so COMMAND will not run.)
[[ -f "$somefile" ]] && echo "Found file: $somefile"
echo "Done."
% ./short-circuit-before-last-line.sh
Done.
% echo $?
0
为什么呢?整个的conf && conmand 的状态是非0的,因为cond不满足条件。作为脚本的最后一行,他将作为程序的退出代码。
这不是我们想要的,同时又是十分微妙的。
解决办法很简单,要么使用完整的if,要么不要将短路放在脚本的最后一行。
短路很简洁,总是让人喜欢使用,但是还得注意。