0%

linux运维学习笔记:如何编写shell脚本

写在前面

这则笔记主要整理shell脚本撰写的知识,主要包括:

  • shell脚本介绍

  • date命令

  • shell判断

  • shell循环

  • shell函数

  • shell数组

shell脚本介绍

shell脚本(shell script),是一种为shell编写的脚本程序。我们可以把需要执行的命令写在脚本当中,系统通过读取脚本执行命令。

shell脚本的特点如下;

  • shell是一种脚本语言;

  • 可以使用逻辑判断、循环等语法;

  • 可以自定义函数;

  • shell是系统命令的集合;

  • shell脚本可以实现自动化运维,能大大增加我们的运维效率;

关于shell脚本,有一些约定俗成的规矩:

  • 脚本通常都以 .sh 作为后缀名;

  • 注释虽然不是必须的,但为了便于后期维护和管理,强烈建议每个脚本都要写注释;

  • 脚本建议统一放在/usr/local/sbin/ ,便于维护和管理;

shell脚本结构

一个shell脚本一般包括三个部分:

  • 第一部分:#!/bin/sh,指定脚本解释器。

  • 第二部分:# 开头的是注释。

  • 第三部分:脚本代码。

shell脚本执行方式

方式一:

1
2
3
4
5
//更改权限
chmod 755 a.sh

//执行脚本
a.sh

方法二:

1
2
3
4
5
6
7
8
//执行脚本
bash a.sh

//查看脚本执行过程
bash -x a.sh

//查看脚本是否有语法错误
bash -n a.sh

shell脚本的预备知识:date 命令

date命令 是在shell脚本当中最常用的命令,用来显示或者设置系统的日期和时间。

命令行格式: date [参数] [+格式]

参数 含义
-d –date=字符串:显示指定字符串所描述的时间,而非当前时间
-f –file=日期文件:类似–date,从日期文件中按行读入时间描述
-r –reference=文件:显示文件指定文件的最后修改时间
-R –rfc-2822:以RFC 2822格式输出日期和时间
格式 含义
%y 以两位数字表示年份(00-99)
%Y 以四位数字表示年份
%m 月份(01-12)
%d 按月计的日期(例如:01)
%j 按年计的日期(0-366)
%c 当前locale 的日期和时间 (如:2005年3月3日 星期四 23:05:25)
%b 当前locale 的月名缩写 (如:一,代表一月)
%a 当前locale 的星期名缩写(例如: 日,代表星期日)
%A 当前locale 的星期名全称 (如:星期日)
%H 小时(00-23)
%M 分(00-59)
%S 大写S,表示秒(00-60)
%s 小写s,自UTC 时间 1970-01-01 00:00:00 以来所经过的秒数
%w 小写w,一星期中的第几日(0-6),0 代表周一
%W 大写W,一年中的第几周,以周一为每星期第一天(00-53)

具体用法示例:

显示时间,可以设定显示格式 date + [格式]

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
//显示年
[root@local-linux00 ~]# date +%Y
2018

//用两位数字表示的年份
[root@local-linux00 ~]# date +%y
18

//显示月份
root@local-linux00 ~]# date +%m
07

//显示日期
root@local-linux00 ~]# date +%d
11
[root@local-linux00 ~]# date +%D
07/11/18
[root@local-linux00 ~]# date +%Y%m%d
20180711
[root@local-linux00 ~]# date +%F
2018-07-11

//显示小时
[root@local-linux00 ~]# date +%H
22

//显示分钟
[root@local-linux00 ~]# date +%M
10

//显示秒
[root@local-linux00 ~]# date +%S
19

//时间戳(距离19700101过去多少秒)
[root@local-linux00 ~]# date +%s
1531318421
[root@local-linux00 ~]# date -d @1517637599
Sat Feb 3 13:59:59 HKT 2018
[root@local-linux00 ~]# date +%s -d "2018-02-03 13:59:59"
1517637599

//显示时间
[root@local-linux00 ~]# date +%T
22:14:40
[root@local-linux00 ~]# date +%H:%M:%S
22:14:59

//显示英文月份
[root@local-linux00 ~]# date +%h
Jul

//显示星期几
[root@local-linux00 ~]# date +%w
3
//今年的第几周
[root@local-linux00 ~]# date +%W
28

//显示日历
[root@local-linux00 ~]# cal
July 2018
Su Mo Tu We Th Fr Sa
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

加减操作 -d 参数

1
2
3
4
5
6
7
date +%Y%m%d                   //显示前天年月日
date -d "+1 day" +%Y%m%d //显示前一天的日期
date -d "-1 day" +%Y%m%d //显示后一天的日期
date -d "-1 month" +%Y%m%d //显示上一月的日期
date -d "+1 month" +%Y%m%d //显示下一月的日期
date -d "-1 year" +%Y%m%d //显示前一年的日期
date -d "+1 year" +%Y%m%d //显示下一年的日期

一天前的日期:

1
date date -d "-1 day" +%d

一小时前:

1
date date -d "-1 hour" +%H

一分钟前;

1
date date -d "-1 min" +%M

设定时间:-s 选项,

注:设置当前时间,只有root权限才能设置,其他只能查看

1
2
3
4
5
6
date -s 20120523               //设置成20120523,这样会把具体时间设置成空00:00:00
date -s 01:01:01 //设置具体时间,不会对日期做更改
date -s "01:01:01 2012-05-23" //这样可以设置全部时间
date -s "01:01:01 20120523" //这样可以设置全部时间
date -s "2012-05-23 01:01:01" //这样可以设置全部时间
date -s "20120523 01:01:01" //这样可以设置全部时间

编写第一个脚本

1
2
3
4
5
6
7
8
9
cd /usr/local/sbin

vi a.sh

#!/bin/bash
# first shell script
echo "123"
w
ls

执行脚本效果

查看脚本执行过程

示例:

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
```

## 脚本变量

变量格式: `var=value` 其中,var是变量名,value是变量内容;

跟其他的编程语言,比如python、javascript了类似,以下情况均考虑使用变量:

* 某个字符串需要频繁使用,且很长;

* 使用条件语句时 `if [ condition ]; then ... ; fi;`

* 引用命令的结果时,用变量替代 `n=wc -l 1.txt`

* 写用户交互脚本,比如 `read -p "Input a number: " n; echo $n` //如果没写这个n,可以直接使用`$REPLY`;

* 内置变量 `$0, $1, $n` 其中,`$0`表示脚本本身,`$1` 第一个参数,`$n`表示参数个数;

* 数学运算a=1;b=2; `c=$(($a+$b))`或者`$[$a+$b]`


示例:

![](https://farm1.staticflickr.com/975/39925240990_c2e22deb07_o.png)

**注:**引用变量时,要用双引号 "" ,否则 $var 会被当作字面量输出;

### 数字运算

**注:**数学计算要用方括号 [] 括起来;同时,引用变量需要加 $

示例:

cat sum1.sh

#! /bin/bash //shebang

get sum of two numbers //注释:求和

m=3
n=5

sum=$[$m+$n]

echo ‘The sum is $sum.’

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

### 获取用户输入

**注:**相当于只定义变量,不赋值;value以参数的形式传入。

示例:

这里用到了rea命令,read 命令从键盘读取变量的值,通常用在shell脚本中与用户进行交互的场合。

命令行格式:`read [选项] [参数]` 参数指的就是变量名,可以跟多个变量名;

常用选项| 含义
---|---
-p|“提示信息” :在等待键盘输入时,输出提示信息,方便用户输入
-t|秒数:指定等待时间,防止read命令一直等待用户输入
-s|输入的数据不显示在监视器上(实际上,数据是显示的,只是read命令将文本颜色设置成与背景相同的颜色)
-n|字符数:指定输入的字符数,只要用户输入指定的字符数,该read命令立即执行完毕

#! /bin/bash

Using ‘read’ in shell scripts

read -p “please input a number: “ x
read -p “Input another number: “ y

sum=$[$x+$y]

echo “$x + $y = $sum”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

### 预设变量

shell变量没有数据类型的区别,变量中的值都是以字符串的形式保存的,按照使用环境大概有以下几种:

* 环境变量:用于保存操作系统运行时使用的环境参数

* 位置变量:Bash将传递给脚本的参数保存在位置变量中,以便于在脚本中引用这些参数

* 预定义变量:由系统保留和维护的一组特殊的变量,这些变量通常用于保存程序运行状态等

* 自定义变量:由用户自行定义的变量,可用于用户编写的脚本,多个命令间的值传递等


预设变量|含义
---|---
$0|保存当前程序或脚本的名称
$*|保存传递给脚本或进程的所有参数
$$|当前进程给脚本的PID号
$!|后台运行的最后一个进程的PID号
$?|用于返回上一条命令是否成功执行。如果这个变量的值为0,说明上一个命令正确执行;如果这个变量的值为非0,则说明上一个命令执行有错误
$#|用于保存脚本的参数个数

示例:

#! /bin/bash

This script contains $0

echo “$1 $2 $0” $0指的是脚本的名字

sum=$[$1+$2]

echo “$1 + $2 = $sum”

1
2
3
4
5
6
7
8

## shell脚本中的逻辑判断

这里的逻辑判断特指if判断,跟python等其他的编程语言用法类似,大概想到如果满足条件,就执行以下操作。常见的if判断有一下三种形式,复杂程度依次上升。

### if判断常见的三种形式

#### 第一种形式

if 条件 ; then 语句; fi

1
2

示例:

[root@local-linux01 ~]# if true; then echo ‘hello’; fi
hello

1
2
3


#### 第二种形式:

if 条件;
then
语句;
else
语句;
fi

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

示例:

```shell
#!/bin/bash
# if-practice

a=2
if
[ $a -gt 3 ]
then
ehco 'A is greater than 3.'
else
echo "No, It's not true."
fi

执行效果

1
2
[root@local-linux01 sbin]# bash if*.sh
No, It's not true.

第三种形式:

1
2
3
4
5
6
7
8
9
if 条件; 
then
语句;
elif 条件;
then
语句;
else
语句;
fi

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
a=10
b=20

if [ $a == $b ]
then
echo "a is equal to b"
elif [ $a -gt $b ]
then
echo "a is greater than b"
elif [ $a -lt $b ]
then
echo "a is less than b"
else
echo "None of the condition met"
fi

注意:

  • if 判断中,expression 和方括号([ ])之间必须有空格,否则会有语法错误;

  • 这里的条件[ expression ],其实返回的是一个布尔值,如果 expression 返回 true,then 后边的语句将会被执行;如果返回 false,就不会执行;

  • [ ]中不能使用<,>,==,!=,>=,<=这样的符号

  • 判断脚本最后必须以 fi 来结尾闭合 if,否则会报错。

文件目录属性判断

表达式 含义
[ -a FILE ] 如果 FILE 存在则为真
[ -b FILE ] 如果 FILE 存在且是一个块文件则返回为真
[ -c FILE ] 如果 FILE 存在且是一个字符文件则返回为真
[ -d FILE ] 如果 FILE 存在且是一个目录则返回为真;
[ -e FILE ] 如果 指定的文件或目录存在时返回为真。
[ -f FILE ] 如果 FILE 存在且是一个普通文件则返回为真。
[ -g FILE ] 如果 FILE 存在且设置了SGID则返回为真。
[ -h FILE ] 如果 FILE 存在且是一个符号符号链接文件则返回为真。(该选项在一些老系统上无效)
[ -k FILE ] 如果 FILE 存在且已经设置了冒险位则返回为真。
[ -p FILE ] 如果 FILE 存并且是命令管道时返回为真。
[ -r FILE ] 如果 FILE 存在且是可读的则返回为真。
[ -s FILE ] 如果 FILE 存在且大小非0时为真则返回为真。
[ -u FILE ] 如果 FILE 存在且设置了SUID位时返回为真。
[ -w FILE ] 如果 FILE 存在且是可写的则返回为真。(一个目录为了它的内容被访问必然是可执行的)
[ -x FILE ] 如果 FILE 存在且是可执行的则返回为真。
[ -O FILE ] 如果 FILE 存在且属有效用户ID则返回为真。
[ -G FILE ] 如果 FILE 存在且默认组为当前组则返回为真。(只检查系统默认组)
[ -L FILE ] 如果 FILE 存在且是一个符号连接则返回为真。
[ -N FILE ] 如果 FILE 存在 and has been mod如果ied since it was last read则返回为真。
[ -S FILE ] 如果 FILE 存在且是一个套接字则返回为真。
[ FILE1 -nt FILE2 ] 如果 FILE1 比 FILE2 新, 或者 FILE1 存在但是 FILE2 不存在则返回为真。
[ FILE1 -ot FILE2 ] 如果 FILE1 比 FILE2 老, 或者 FILE2 存在但是 FILE1 不存在则返回为真。
[ FILE1 -ef FILE2 ] 如果 FILE1 和 FILE2 指向相同的设备和节点号则返回为真。

示例:

1
2
3
4
5
6
7
8
9
cat if-file.sh
#!/bin/bash
f="/tmp/if-test"
if [ -f $f ]
then
echo $f exist
else
touch $f
fi

执行效果

1
2
3
4
5
[root@local-linux01 sbin]# bash -x !$
bash -x if-file.sh
+ f=/tmp/if-test
+ '[' -f /tmp/if-test ']'
+ touch /tmp/if-test

if特殊用法

表达式 含义
[ -z STRING ] 如果STRING的长度为零则返回为真,即空是真
[ -n STRING ] 如果STRING的长度非零则返回为真,即非空是真
[ STRING1 ]  如果字符串不为空则返回为真,与-n类似

示例:

1
2
3
4
5
6
7
8
9
10
#!/bin/bash
n=`wc -l /tmp/if-test`
if [ -z "$n" ]
then
echo error
exit
elif [ $n -gt 100 ]
then
echo 'greater than 100'
fi

执行效果

1
2
3
4
5
6
7
[root@local-linux01 sbin]# bash -x if03.sh
++ wc -l
++ cat /tmp/if-test
+ n=0
+ '[' -z 0 ']'
+ echo 0
0

case判断

case判断有点像选择题,给你一堆选择,根据你的选择,执行不同的操作。

格式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
case "varname" in
option1)
command
exit
;;
option1)
command
exit
;;
option1)
command
exit
;;
option1)
command
exit
;;
*)
command
exit
;;
esac

注意:

  • case需要一个esac(就是case反过来)作为结束标记,每个case分支用右圆括号,用两个分号表示break.

  • *) 相当于其他语言中的default;除了*)模式,各个分支中;;是必须的,;;相当于其他语言中的break;

示例:

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
#!/bin/bash
read -p "Please input a number: " n
if [ -z "$n" ]
then
echo "Please input a number."
exit 1
fi

n1=`echo $n|sed 's/[0-9]//g'`
if [ -n "$n1" ]
then
echo "Please input a number."
exit 1
fi

if [ $n -lt 60 ] && [ $n -ge 0 ]
then
tag=1
elif [ $n -ge 60 ] && [ $n -lt 80 ]
then
tag=2
elif [ $n -ge 80 ] && [ $n -lt 90 ]
then
tag=3
elif [ $n -ge 90 ] && [ $n -le 100 ]
then
tag=4
else
tag=0
fi

case $tag in
1)
echo "not ok"
;;
2)
echo "ok"
;;
3)
echo "ook"
;;
4)
echo "oook"
;;
*)
echo "The number range is 0-100."
;;
esac

执行效果

1
2
3
4
[root@local-linux01 sbin]# bash !$
bash case01.sh
Please input a number: 1
not ok

执行过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[root@local-linux01 sbin]# bash -x !$
bash -x case01.sh
+ read -p 'Please input a number: ' n
Please input a number: 4
+ '[' -z 4 ']'
++ echo 4
++ sed 's/[0-9]//g'
+ n1=
+ '[' -n '' ']'
+ '[' 4 -lt 60 ']'
+ '[' 4 -ge 0 ']'
+ tag=1
+ case $tag in
+ echo 'not ok'
not ok

shell循环

跟python其他的高级语言类似,shell脚本也有for循环、while循环,以及break、continue。

for循环

格式:

1
2
3
for 变量名 in 循环条件;do
command
done
  • 示例1:
1
2
3
4
5
#!/bin/bash
for i in `seq 1 100 `
do
echo $i
done
  • 示例2:累加计算
1
2
3
4
5
6
7
8
#!/bin/bash
sum=0
for i in `seq 1 100`
do
echo "$sum + $i"
sum=$[$sum+$i]
echo $sum
done
  • 示例3:遍历文件夹
1
2
3
4
5
6
7
8
9
10
#!/bin/bash
cd /etc/
for a in ls /etc/
do
if [ -d $a ]
then
echo $a
ls $a
fi
done

while循环

while循环相当于一种特殊的for循环,当满足条件时,会一直执行,直到不再满足条件为止。

格式:

1
2
3
4
while condition
do
command
done

如果将condition改为布尔值true,就会变成无限循环,一直执行下去。格式可以写成一下三种形式:

1
2
3
4
while :
do
command
done

或者

1
2
3
4
while true
do
command
done

或者

1
for (( ; ; ))
  • 示例1:
1
2
3
4
5
6
7
8
9
10
#!/bin/bash
while true
do
load=`w|head -1|awk -F 'load average: ' '{print $2}'|cut -d. -f1`
if [ $load -gt 10 ]
then
/usr/local/sbin/mail.py xxx@qq.com "load high" "$load"
fi
sleep 30
done
  • Samples2:在循环过程中需要人为的输入一个数字
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/bin/bash
while :
do
read -p "Please input a number: " n
if [ -z "$n" ]
then
echo "you need input sth."
continue
fi
n1=`echo $n|sed 's/[0-9]//g'`
if [ ! -z "$n1" ]
then
echo "you just only input numbers."
continue
fi
break
done

break跳出循环

适用场景:将判断和循环结合起来,符合某些条件时break中断循环。注:break只会影响本次循环,在有多重循环嵌套的情况,break只会影响本层级的循环,它的上层循环不受影响。

samples:

1
2
3
4
5
6
7
8
9
10
#!/bin/bash
for i in `seq 1 5`
do
echo $i
if [ $i -eq 3 ]
then
break
fi
echo $i
done

continue结束本次循环

忽略continue之下的代码,直接进行下一次循环

samples:

1
2
3
4
5
6
7
8
9
10
#!/bin/bash
for i in `seq 1 5`
do
echo $i
if [ $i -eq 3 ]
then
continue
fi
echo $i
done

exit退出整个脚本

跟break不同之处,在于使用exit语句,会直接退出整个shell脚本。

samples:

1
2
3
4
5
6
7
8
9
10
#!/bin/bash
for i in `seq 1 5`
do
echo $i
if [ $i -eq 3 ]
then
exit
fi
echo $i
done

注:运行exit语句,直接退出shell脚本,后面的语句都不执行。

shell中的函数

linux shell 可以用户定义函数,然后在shell脚本中可以随便调用。

用法:

  • 第一种:
1
2
3
function function_name { 
command
}
  • 第二种:

示例

samples1:定义函数打印参数

1
2
3
4
5
6
7
8
#!/bin/bash
function inp(){
echo "The first par is $1"
echo "The second par is $2"
echo "The third par is $3"
echo "the scritp name is $0"
echo "the number of par is $#"
}

samples2:加法函数

1
2
3
4
5
#!/bin/bash
sum() {
s=$[$a+$b]
echo $s
}

samples3:显示IP

1
2
3
4
5
6
7
#!/bin/bash
ip(){
ifconfig |grep -A1 "$1: "|awk '/inet/ {print $2}'
}

read -p "Please input the eth name: " eth
ip $eth

shell中的数组

在Shell中,用括号来表示数组,数组元素用空格符号分割开。定义数组的一般形式为:

1
array_name=(value1 value2... valuen)

bash支持一维数组(不支持多维数组),并且没有限定数组的大小。与C语言类似,shell数组元素的下标由0开始编号。获取数组中的元素要利用下标,下标可以是整数或算术表达式,其值应大于或等于0。

定义数组

  • 第一种:
1
array_name=(val0 val1 val2 val3)
  • 第二种:
1
2
3
4
5
6
array_name=(
val0
val1
val2
val3
)
  • 第三种:也可以单独定义变量
1
2
3
array_name[0]=value0
array_name[1]=value1
array_name[2]=value2

读取数组

格式为:

1
${array_name[index]}

samples:

1
2
3
4
5
6
7
8
9
10
#!/bin/sh
name=(
Zara
Qadir
Mahnaz
Ayan
Daisy
)
echo "First Index: ${name[0]}"
echo "Second Index: ${name[1]}"

执行效果

1
2
3
[root@local-linux01 sbin]# bash array01.sh
First Index: Zara
Second Index: Qadir

获取数组长度

示例:

执行效果

删除数组元素

1
2
3
4
5
//删除数组
unset arrayName

//删除数组元素
unset arrayName[n]

samples:

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/sh
name=(
Zara
Qadir
Mahnaz
Ayan
Daisy
)

echo ${name[*]}
echo "echo delete second name:${name[2]}"
unset name[2]
echo $"The name remains: ${name[*]}"

执行效果

1
2
3
4
5
[root@local-linux01 sbin]# vi array01.sh
[root@local-linux01 sbin]# bash array01.sh
Zara Qadir Mahnaz Ayan Daisy
echo delete second name:Mahnaz
The name remains: Zara Qadir Ayan Daisy
-------------本文结束感谢您的阅读-------------
坚持原创技术分享,您的支持将鼓励我继续创作!