Shell脚本编程的高级技巧与陷阱
注:本文的Shell既可表示外壳(相对于操作系统内核而言)接口解释器,
也可表示其语法规范,且以桌面版(相对于裁剪过的嵌入式版本更完整)bash为主,
尽量兼容sh,至于csh、zsh等暂不涉及。
1、条件判断
test、[、[[的区别:[是test的同义词(均可通过执行man test来查看其使用说明),但[必须与]配对使用。[[是bash特有(sh无)的语法,比[更强大(但若考虑兼容性则仍推荐[), 例如:- 扩充了条件表达式的写法:
- 例1:既支持
[[ $var -gt 1 ]](或使用[]),也支持[[ $var > 1 ]]。 - 例2:既支持
[[ $var -gt 1 -a $var -lt 5 ]](或使用[]。另:逻辑“或”使用-o, 逻辑“非”使用!),也支持[[ $var > 1 && $var < 5 ]]。
- 例1:既支持
- 容错性更高,但也同时带来一些隐患:例如上面的
var若未定义则$var为空, 按NUL字符的ASCII码与1的ASCII码进行比较,不会报错,但[语法会报错, 两者有利有弊,可根据使用场景进行选用。
- 扩充了条件表达式的写法:
- 应使用单等号(
=)还是双等号(==):推荐前者,后者在sh中会报错(unexpected operator)。
2、变量
-
变量赋值时,等号两边不允许有空格,即只能写成
VAR=VALUE,而不能写成VAR = VALUE。 -
只有函数内部的变量才允许使用
local关键字修饰。 $*作为一个参数,$@作为多个参数(以数组的形式),两者在使用双引号括起时区别最明显,例如:$ cat test1.sh for i in "$*" do echo "${i}" done $ $ sh test1.sh aa "bb cc" dd ee aa bb cc dd ee $ $ cat test2.sh for i in "$@" do echo "${i}" done $ $ sh test2.sh aa "bb cc" dd ee aa bb cc dd ee- 特殊变量:
$*、$@、$#:$*和$@见上面,$#表示位置参数(见下面)的数量。$1、$2、$3、……:位置参数。注意编号为两位数以上的参数,最好用花括号将编号括起。$0:当前脚本路径。$?:上一个操作的返回值。$$:当前进程(既可为交互式Shell环境,也可为运行中的脚本)的进程号(即PID)。$!:截至目前在当前Shell运行的最后一个后台进程的PID。
- 确保变量值非空:可使用
${VAR-FALLBACK}形式来获取变量值,例如:$ echo ${nonexistent_var-OOXX} OOXX
3、字符串
- 删除:以
filename=aa.bb.cc.txt为例:- 删除前缀(保留后缀):
- 从左到右非贪婪匹配:
$ echo ${filename#*.} bb.cc.txt - 贪婪匹配:
$ echo ${filename##*.} txt
- 从左到右非贪婪匹配:
- 删除后缀(保留前缀):
- 从右到左非贪婪匹配:
$ echo ${filename%.*} # 与Makefile的“茎”语法类似,可借此助记 aa.bb.cc - 贪婪匹配:
$ echo ${filename%%.*} aa
- 从右到左非贪婪匹配:
- 删除前缀(保留后缀):
- 替换:以
filename=aa.bb.cc.txt和path=/aa/bb/cc/dd为例:- 非贪婪匹配:
$ echo ${filename/./-} aa-bb.cc.txt $ $ echo ${path/\//\\} \aa/bb/cc/dd - 贪婪匹配:
$ echo ${filename//./-} aa-bb-cc-txt $ $ echo ${path//\//\\} \aa\bb\cc\dd
- 非贪婪匹配:
-
获取子符串长度:
${#string} - 提取子串:
${string:<start>:<count>},不能使用负数作为索引。
4、顺序型数组
-
获取数组长度:
${#array[@]} -
提取子集:
${array[@]:<start>:<count>},不能使用负数作为索引。 -
访问单个元素:
${array[<i>]},正数索引范围为[0, count),>= count不会报错但值为空; 负数索引范围为[-count, -1],< -count会报错。 -
获取所有索引(下标):
${!array[@]} - 判断是否包含某元素:可使用
正则等号(即=~)和双层方括号(即[[和]]),有2种写法:[[ " ${array[*]} " =~ " xx " ]]:通过在两端添加空格,使每个元素具有相同的格式。[[ ${array[*]} =~ (^|[[:space:]])xx($|[[:space:]]) ]]:写法更复杂, 但仍是对正则表达式的运用。
-
删除元素:
unset array[<i>]。特别地,删除最后一个元素既可用unset array[$((${#array[@]} - 1))], 也可用unset array[-1]。 - 关于顺序型数组更详细的介绍可查阅这篇文章或其备份文档。
5、关联数组
待补充
6、控制选项
-
在交互式环境(即命令行界面)默认启用此机制;非交互式环境(即脚本)则默认禁用, 若要启用则需要使用
shopt -s expand_aliases。
7、正则表达式
- 若要在
grep命令使用水平制表符,则不能直接用\t,而要用$'\t', 但表达式稍复杂一点可能会显得很混乱,所以在允许同时搜空格和水平制表符的前提下, 更推荐使用[[:blank:]](等同于[ \t])或[[:space:]](等同于[ \t\r\n\v\f])。