每个脚本中我都以下面的内从开始:
#!/bin/bash
set -o nounset
set -o errexit
这会处理两个常见的错误:
这两个设置是有对应快捷写法的(”-u”和”-e”),但是原始写法更佳易读。
如果你要忽略可能执行错误的命令,可以使用下面的写法:
if ! <possible failing command> ; then
echo "failure ignored"
fi
需要注意的是,有些Linux命令可以使用一些选项来强制忽略错误,比如rm -f
和mkdir -p
。
还需要注意的是,在“errexit”模式下,虽然能有效捕捉错误,但不能捕捉全部错误。在特定情况下,有些失败的命令没办法检测。(更多信息可以参考这篇文章)
一位读者还推荐另一个用法set -o pipefail
在Bash中你可以定义其它函数,它们和其它命令一样—你可以随意调用它们;这也会让你的脚本更具可读性。
ExtractBashComments() {
egrep "^#"
}
cat myscript.sh | ExtractBashComments | wc
comments=$(ExtractBashComments < myscript.sh)
更多例子:
SumLines() { # iterating over stdin - similar to awk
local sum=0
local line=””
while read line ; do
sum=$((${sum} + ${line}))
done
echo ${sum}
}
SumLines < data_one_number_per_line.txt
log() { # classic logger
local prefix="[$(date +%Y/%m/%d\ %H:%M:%S)]: "
echo "${prefix} $@" >&2
}
log "INFO" "a message"
尝试把所有bash代码移植到函数中,只留下全局变量/常量,然后在main函数中统一调用它们。
Bash允许一种有限制的变量注解形式,最终要的有:
# a useful idiom: DEFAULT_VAL can be overwritten # with an environment variable of the same name readonly DEFAULT_VAL=${DEFAULT_VAL:-7} myfunc() { # initialize a local variable with the global default local some_var=${DEFAULT_VAL} ... }
这样,你就可以把以前不是只读的变量声明成只读变量:
x=5
x=6
readonly x
x=7 # failure
尽量把bash中的所有变量注解成readonly
或者local
。
反单引号在一些字体中难以辨识,很容易和单引号混淆。
$()允许内嵌,而且避免了转义的麻烦
# both commands below print out: A-B-C-D
echo "A-`echo B-\`echo C-\\\`echo D\\\`\``"
echo "A-$(echo B-$(echo C-$(echo D)))"
[[]]
能够避免文件扩展名异常,提供一些语法上的改进,还增加了一些新的特性:
Operator(操作符) | Meaning(含义) | ||
---|---|---|---|
逻辑或 | |||
&& | 逻辑与 | ||
< | 字符比较(双中括号中不需要转义) | ||
-lt | 数字比较 | ||
= | 字符串比较 | ||
== | 以globbing的方式比较字符串,见下文 (仅双中括号有效) | ||
=~ | 正则方式比较字符串,见下文(仅双中括号有效) | ||
-n | 字符串非空 | ||
-z | 字符串为空 | ||
-eq | 数字相等 | ||
-ne | 数字不相等 |
单中括号:
[ "${name}" \> "a" -o ${name} \< "m" ]
双中括号:
[[ "${name}" > "a" && "${name}" < "m" ]]
以下几个例子能够体现出双中括号的强大能力:
t="abc123"
[[ "$t" == abc* ]] # true (globbing)
[[ "$t" == "abc*" ]] # false (literal matching)
[[ "$t" =~ [abc]+[123]+ ]] # true (regular expression)
[[ "$t" =~ "abc*" ]] # false (literal matching)
注意,bash3.2以后正则表达式或Globbing表达式不能被引号包裹。如果表达式中含有空格,你可以存到变量中:
r="a b+"
[[ "a bbb" =~ $r ]] # true
基于Globbing的字符串比较也可以用到case中:
case $t in
abc*) <action> ;;
esac
bash有很多操作字符串的方法。
f="path1/path2/file.ext" # deletion at string beginning extension="${f#*.}" # = "ext" # greedy deletion at string beginning filename="${f##*/}" # = "file.ext" # deletion at string end dirname="${f%/*}" # = "path1/path2" # greedy deletion at end root="${f%%/*}" # = "path1"
一些命令使用文件名作为参数,所以管道就无法使用了。这时<()
就派上用场了,它可以接受一个命令,然后把命令转换成可以作为文件名的东西:
# download and diff two webpages
diff <(wget -O - url1) <(wget -O - url2)
“here document”也很有用,它允许把任意行字符串传给标准输入。
下面的MARKER可以换成任何文本:
# DELIMITER is an arbitrary string
command << MARKER
...
${var}
$(cmd)
...
MARKER
对脚本进行语法检查
bash -n myscript.sh
跟踪脚本里每个命令的执行:
bash -v myscript.sh
跟踪脚本里每个命令的执行并附加扩充信息:
bash -x myscript.sh
你可以在脚本头部添加set -o verbose
和set -o xtrace
来永久指定-x
和-v
。
如果脚本运行在远程机器上这会很有效,用它来输出远程信息。
你需要考虑Python或者Ruby这样的脚本语言
本文介绍了Bash中很多好的编程习惯和经验,字符串操作和比较是容易忽视以及不易掌握的。 注意bash中的正则和globbing的区别。
另外,本文有很多国人翻译了,译者在翻译本文时有一些翻译参考了《Bash脚本15分钟进阶教程》(http://www.vaikan.com/bash-scripting/),这篇翻译质量很高,我个人学习和借鉴了很多。