shell脚本必知必会

变量命名

规则

  1. 首字符必须为字母
  2. 中间不能有空格, 可以有下划线_
  3. 不能使用标点符号
  4. 不能使用bash里的关键字

代码风格建议

  • 全部用大写
  • 下划线分隔
  • 定义在文件的顶部

变量赋值

变量名=值, (注意, = 两边不能有空格)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var=1
var=$var+1
echo $var+1
# 输出为1+1, 而不是2

# 可以用如下方法使其输出为2
let "var+=1"
#var=$[$var+1]
#var=$(($var+1))
echo $var
或者
var=1
var= expr $var + 1 #(注意, + 两边的空格, 一定要有)

let表示数学运算, expr用于整数值运算, 每一项用空格隔开, $[]将中括号内的表达式作为数学运算先计算结果再输出.
在bash中, 将数学运算结果赋给某个变量, var=$[ operation ]

变量自增, 自减
let var++ let var--
let var+=2

echo "10.2-2" | bc -- 小数运算要用bc $[]不支持小数

变量使用

$var ${var}
双引号“ “ 中可以用$var
单引号‘ ‘ 中不可以用$var

$(cmd) 与 `cmd` 等效, 推荐用前者

  • 支持内嵌
  • 不用转义
  • 有些字体, 反引号和单引号很像, 让人看着很难受

内建变量

1
2
3
4
5
6
7
- $RANDOM  随机数
- 字段分隔符, IFS=$'n'
- $0 程序名, $1$9 是命令行参数, 多于9个命令行参数的话, 后面的需要${10} ${11}这种格式
- $# 表示传入的命令行参数的个数. 在{}中使用$#时, 要改用${!#}.
- $* 所有命令行参数当成一个单词存储
- $@ 所有命令行参数当成一个字符串中的多个单词
- $$ 脚本的PID

比较运算符

文件比较运算符

1
2
3
4
5
6
7
8
9
10
-e filename                如果 filename 存在, 则为真               [ -e /var/log/syslog ]
-d filename 如果 filename 为目录, 则为真 [ -d /tmp/mydir ]
-f filename 如果 filename 为常规文件, 则为真 [ -f /usr/bin/grep ]
-L filename 如果 filename 为符号链接, 则为真 [ -L /usr/bin/grep ]
-r filename 如果 filename 可读, 则为真 [ -r /var/log/syslog ]
-w filename 如果 filename 可写, 则为真 [ -w /var/mytmp.txt ]
-x filename 如果 filename 可执行, 则为真 [ -L /usr/bin/grep ]
! -e filename 如果 filename 不存在, 则为真 [ ! -e /var/log/syslog ]
filename1 -nt filename2 如果 filename1 比 filename2 新, 则为真 [ /tmp/install/etc/services -nt /etc/services ]
filename1 -ot filename2 如果 filename1 比 filename2 旧, 则为真 [ /boot/bzImage -ot arch/i386/boot/bzImage ]

字符串比较运算符 (请注意引号的使用, 这是防止空格扰乱代码的好方法)

1
2
3
4
-z string               如果 string 长度为零, 则为真            [ -z "$myvar" ]
-n string 如果 string 长度非零, 则为真 [ -n "$myvar" ]
string1 = string2 如果 string1 与 string2 相同, 则为真 [ "$myvar" = "one two three" ]
string1 != string2 如果 string1 与 string2 不同, 则为真 [ "$myvar" != "one two three" ]

算术比较运算符

1
2
3
4
5
6
num1 -eq num2       等于            [ 3 -eq $mynum ]
num1 -ne num2 不等于 [ 3 -ne $mynum ]
num1 -lt num2 小于 [ 3 -lt $mynum ]
num1 -le num2 小于或等于 [ 3 -le $mynum ]
num1 -gt num2 大于 [ 3 -gt $mynum ]
num1 -ge num2 大于或等于 [ 3 -ge $mynum ]

结构语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
if command
then
commands
fi

if command1 && command2
then
commands
fi

if command; then #如果then与if在同一行, if command后要加';'
commands
fi

if command
then
commands
else
commands
fi

if command1
then
commands
elif command2
then
commands
fi

# 不推荐
if test condition
if [ condition ] 注意[]与condition之间的空格, > < 需要转义
then
commands
fi

# 不推荐
if ((expression)) > < 不需要转义
then
commands
fi

# 推荐用 [[ ]] 的形式
# 1. 避免转义问题
# 2. 有新功能, 包括但不限于以下
# - ||: 逻辑or
# - &&: 逻辑and
# - <: 字符串比较(不需要转义)
# - ==: 通配符字符串比较
# - =~: 正则表达式字符串比较
if [[ condition ]] 可以用正则表达式
then
commands
fi

t="abc123"

[[ "$t" == abc* ]] #true
[[ "$t" == "abc*" ]] # false
[[ "$t" =~ [abc]+[123]+ ]] # true
[[ "$t" =~ "abc*" ]] # false

case variable in
pattern1 | pattern2)
commands1
;;
pattern3)
commands2::
;;
*)
default commands
;;
esac


for var in list
do
commands
done

while test command
do
other commands
done

until test command
do
other commands
done

break n (default 1) 跳出n层循环
continue n (default 1) 继续n级循环

select var in list
do
commands
done

函数

语法

1
2
3
4
5
[ function ] funname [()]
{
action;
[return int;]
}

说明:

  1. 可以带function fun() 定义, 也可以直接fun() 定义, 不带任何参数.
  2. 参数返回, 可以显式return返回, return后跟数值n(0-255);如果不加, 将以最后一条命令运行结果, 作为返回值.
1
2
3
4
5
6
7
8
9
10
11
# 将所有的错误信息导向STDERR

err() {
echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')]: $@" >&2
}

if ! do_something; then
err "Unable to do_something"
exit "${E_DID_NOTHING}
fi

注意事项

  1. 必须在调用函数地方之前, 声明函数, shell脚本是逐行运行. 不会像其它语言一样先预编译. 一次必须在使用函数前先声明函数.
  2. total=$(func 3 2);
  3. 函数返回值, 只能通过$? 系统变量获得, 直接通过=获得是空值
  4. 如果需要传出其它类型函数值, 可以在函数调用之前, 定义变量(这个就是全局变量). 在函数内部就可以直接修改, 然后在执行函数就可以读出修改过的值
  5. 如果需要定义自己变量, 可以在函数中定义, local 变量=值, 这时变量就是内部变量, 它的修改, 不会影响函数外部相同变量的值.

实用技巧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#! /bin/bash

# 在默认情况下,遇到不存在的变量,会忽略并继续执行,而这往往不符合预期,加入该选项,可以避免恶果扩大,终止脚本的执行。
set -o nounset

# 在默认情况下,遇到执行出错,会跳过并继续执行,而这往往不符合预期,加入该选项,可以避免恶果扩大,终止脚本的执行
set -o errexit

log() {
local prefix="[$(date +%Y/%m/%d\ %H:%M:%S)]:"
if [[ "$_DEBUG" == "true" ]]; then
echo "${prefix} $@" >&2
fi
}

log "INFO" "a message"