15分钟Bash进阶

说明

更安全的脚本

每个脚本中我都以下面的内从开始:

#!/bin/bash
set -o nounset  
set -o errexit  

这会处理两个常见的错误: 1. 引用未定义的变量(默认是"")
2. 忽略执行失败的命令

这两个设置是有对应快捷写法的("-u"和"-e"),但是原始写法更佳易读。

如果你要忽略可能执行错误的命令,可以使用下面的写法:

if ! <possible failing command> ; then  
    echo "failure ignored"
fi  

需要注意的是,有些Linux命令可以使用一些选项来强制忽略错误,比如rm -fmkdir -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允许一种有限制的变量注解形式,最终要的有:
- local(在函数内定义局部变量) - readonly(只读变量)

# 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

用$() 代替 (`)

反单引号在一些字体中难以辨识,很容易和单引号混淆。

$()允许内嵌,而且避免了转义的麻烦

# 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}
   ...
}

[[]]代替[]

[[]]能够避免文件扩展名异常,提供一些语法上的改进,还增加了一些新的特性:

|Operator(操作符)|Meaning(含义)| |:------:|:------:| | || | 逻辑或 | | && | 逻辑与 | | < | 字符比较(双中括号中不需要转义) | | -lt | 数字比较 | | = | 字符串比较 | | == | 以globbing的方式比较字符串,见下文 (仅双中括号有效)| | =~ | 正则方式比较字符串,见下文(仅双中括号有效) | | -n | 字符串非空 | | -z | 字符串为空 | | -eq | 数字相等 | | -ne | 数字不相等 |

单中括号:

x=5  
x=6  
readonly x  
x=7   # failure  

双中括号: readonly

正则和Globbing

以下几个例子能够体现出双中括号的强大能力: local

注意,bash3.2以后正则表达式或Globbing表达式不能被引号包裹。如果表达式中含有空格,你可以存到变量中: [[]]

基于Globbing的字符串比较也可以用到case中: set -o verbose

字符串操作

bash有很多操作字符串的方法。

  • Basics set -o xtrace

download and diff two webpages

diff <(wget -O - url1) <(wget -O - url2)
-x

DELIMITER is an arbitrary string

command << MARKER
... ${var} $(cmd) ... MARKER
-v bash -n myscript.sh
undefined bash -v myscript.sh
undefined bash -x myscript.sh
```

你可以在脚本头部添加set -o verboseset -o xtrace来永久指定-x-v

如果脚本运行在远程机器上这会很有效,用它来输出远程信息。

什么时候不该用脚本

  • 你的脚本很长,不下于几百行
  • 除了简单的数组外你还需要数据结构
  • 出现复杂的转义问题
  • 需要很多字符串操作
  • 不太需要调用其它程序或者通过管道和其它程序交互
  • 你比较在意性能

你需要考虑Python或者Ruby这样的脚本语言

参考

译者说

本文介绍了Bash中很多好的编程习惯和经验,字符串操作和比较是容易忽视以及不易掌握的。 注意bash中的正则和globbing的区别。

另外,本文有很多国人翻译了,译者在翻译本文时有一些翻译参考了Bash脚本15分钟进阶教程,这篇翻译质量很高,我个人学习和借鉴了很多。