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]
)。