[toc]

6-Shell命令脚本

一、Shell脚本

1、什么时Shell脚本

Shell终端解释器,是人与计算机硬件之间的“翻译官”,它作为用户与Linux系统内部的通信媒介,除了能够支持各种变量与参数外,还提供了诸如循环、分支等高级编程语言才有的控制结构特性。

Shell脚本命令的工作方式有两种:

  • 交互式(Interactive):用户每输入一条命令就立即执行。
  • 批处理(Batch):由用户事先编写好一个完整的Shell脚本,Shell会一次性执行脚本中诸多的命令。

Shell脚本中用到linux命令以及正则表达式、管道符、数据流重定向等语法规则,还需要把内部功能模块化后通过逻辑语句进行处理,最终形成日常所见的Shell脚本。

2、编写简单的脚本

使用Vim编辑器把Linux命令按照顺序依次写入到一个文件中,就是一个简单的脚本。

脚本命名:名称.sh

Shell脚本文件的名称可以任意,建议将.sh后缀加上,以表示是一个脚本文件。

2.1、创建脚本

脚本格式:

#! 系统shell解析器说明 系统使用哪种`shell` 解析器来执行脚本
# 脚本功能与命令说明
命令行
命令行
...
示例2:创建一个简单脚本test.sh,执行pwdls命令
#! /bin/bash
# 简单的脚本,执行输出当前目录路径和目录详情
pwd
ls -la
2.2、执行脚本

执行脚本有两种方式:

  • bash命令直接运行:bash 脚本文件.sh
  • 输入完整路径执行:
    • 绝对路径:/路径/脚本文件.sh
    • 相对路径(相对脚本所在目录):./脚本文件.sh
    • 使用此方式,需设置脚本文件执行权限
示例1:使用bash执行脚本
[root@localhost ~]# bash test.sh 
/root
总用量 332
dr-xr-x---. 16 root root   4096 2月  12 16:29 .
dr-xr-xr-x. 18 root root    236 1月  27 16:24 ..
………………省略部分输出………………
示例2:使用路径执行
[root@localhost test]# ~/test.sh
bash: /root/test.sh: 权限不够

执行时权限不足,查看文件权限信息,文件针对所有用户没有可执行权限。

[root@localhost test]# ls -l ~/test.sh
-rw-r--r--. 1 root root 92 2月  12 16:29 /root/test.sh

改变文件权限,对所有用户授予可执行权限后,再次执行。

# 授予权限
[root@localhost test]# chmod 755 ~/test.sh 
[root@localhost test]# ~/test.sh
总用量 16
drwxr-xr-x.  2 root root   64 2月  19 15:37 .
dr-xr-x---. 16 root root 4096 2月  19 16:29 ..
-rw-r--r--.  1 root root    0 2月  19 15:01 a.txt
-rw-r--r--.  1 root root 2310 2月  19 15:17 b.txt
-rw-r--r--.  1 root root   70 2月  19 15:22 index.html
-rw-r--r--.  1 root root 2307 2月  19 15:37 passwd

3、接收用户的参数

shell脚本语言内设了用于接收参数的变量,变量之间可以使用空格间隔。

内设变量:

  • $0:对应的是当前Shell脚本程序的名称
  • $#:对应的是总共有几个参数
  • $*:对应的是所有位置的参数值
  • $?:对应的是显示上一次命令的执行返回值
  • $1、$2、$3:分别对应着第N个位置的参数值

image

示例:编写一个脚本example.sh,引用变量参数

① 创建脚本

#! /bin/bash
echo "当前脚本名称为$0"
echo "中共有$#个参数,分别是$*。"
echo "第1个参数为$1,第5个为$5。"

② 执行脚本,并传入参数

[root@localhost test]# vim example.sh
[root@localhost test]# bash example.sh one two three four five six
当前脚本名称为example.sh
中共有6个参数,分别是one two three four five six。
第1个参数为one,第5个为five。

4、判断用户的参数

Shell脚本中的条件测试语法可以判断表达式是否成立,若条件成立则返回数字0,否则便返回其他随机数值。

image

条件测试语句分类:

  • 文件测试语句
  • 逻辑测试语句
  • 整数值比较语句
  • 字符串比较语句
4.1、文件测试语句

作用:测试文件是否存在或是否具有权限等语句

参数说明:

| 操作符 | 作用 |
| —— | ————————– |
| -d | 测试文件是否为目录类型 |
| -e | 测试文件是否存在 |
| -f | 判断是否为一般文件 |
| -r | 测试当前用户是否有权限读取 |
| -w | 测试当前用户是否有权限写入 |
| -x | 测试当前用户是否有权限执行 |

示例1:查看etc/fstab是否是一个目录

执行命令后可使用$?变量显示上一条命令执行的结果,返回0是目录,其它非0值不是目录。

[root@localhost test]# [ -d /etc/fstab  ]
[root@localhost test]# echo $?
1  # 返回1表示/etc/fstab不是目录
示例2:查看etc/fstab是否是一个文件
[root@localhost test]# [ -f /etc/fstab  ]
[root@localhost test]# echo $?
0  # 返回1表示/etc/fstab是一个普通文件
4.2、逻辑测试语句

作用:逻辑语句用于对测试结果进行逻辑分析,根据测试结果可实现不同的效果。

逻辑运算符:

  • 逻辑“与”:&&
  • 逻辑“或”:||
  • 逻辑“非”:!
示例1:判断/dev/cdrom文件是否存在,若存在则输出存在
[root@localhost test]# [ -e /dev/cdrom  ] &&  echo "存在"
存在
示例2:结合系统环境变量USER来判断当前登录的用户是否为非管理员身份
[root@localhost test]# [ $USER = root  ] || echo "非管理员"
[root@localhost test]# su tiger
[tiger@localhost test]$  [ $USER = root  ] || echo "非管理员"
非管理员
示例3:切换回到root管理员身份,再判断当前用户是否为一个非管理员的用户。由于判断结果因为两次否定而变成正确。
[root@localhost test]# [ $USER != root  ] || echo "非管理员"
非管理员
示例4:结合系统环境变量USER来判断当前登录的用户是否为管理员身份,如果是输入管理员,否则输出非管理员
[root@localhost test]# [ $USER = root  ] && echo "管理员" || echo "非管理员"
管理员
[root@localhost test]# su tiger
[tiger@localhost test]$ [ $USER = root  ] && echo "管理员" || echo "非管理员"
非管理员
4.3、整数值比较语句

作用:对数字进行比较操作,整数比较运算符仅是对数字的操作,不能将数字与字符串、文件等内容一起操作。

整数比较运算符:

| 操作符 | 作用 |
| —— | ————– |
| -eq | 是否等于 |
| -ne | 是否不等于 |
| -gt | 是否大于 |
| -lt | 是否小于 |
| -le | 是否等于或小于 |
| -ge | 是否大于或等于 |

示例1:10是否大于10以及10是否等于10
[tiger@localhost test]$ [ 10 -gt 10 ]
[tiger@localhost test]$ echo $?
1
[tiger@localhost test]$ [ 10 -eq 10 ]
[tiger@localhost test]$ echo $?
0
示例2:获取当前系统正在使用及可用的内存量信息,如果小于1024则显示内存不足

① 使用free -m命令查看内存使用量情况(单位为MB),然后通过grep Mem:命令过滤出剩余内存量的行,再用awk '{print $4}'命令只保留第四列,最后用FreeMem=`语句`的方式把语句内执行的结果赋值给变量。

[root@localhost test]$ free -m
              total        used        free      shared  buff/cache   available
Mem:           1819         848          91          15         879         789
Swap:          1023           7        1016
[root@localhost test]$ free -m |grep Mem:
Mem:           1819         848          90          15         879         788
[root@localhost test]$ free -m |grep Mem:| awk '{print $4}'
90
[root@localhost test]$ FreeMem=`free -m |grep Mem:| awk '{print $4}'`
[root@localhost test]$ echo $FreeMem
90

② 使用整数运算符来判断内存可用量的值是否小于1024,若小于则会提示“内存不足”

[root@localhost test]$ [ $FreeMem -lt 1024  ] && echo "内存不足"
内存不足
4.4、字符串比较语句

作用:字符串比较语句用于判断测试字符串是否为空值,或两个字符串是否相同。它经常用来判断某个变量是否未被定义(即内容为空值)。

字符串比较运算符:

| 操作符 | 作用 |
| —— | ———————- |
| = | 比较字符串内容是否相同 |
| != | 比较字符串内容是否不同 |
| -z | 判断字符串内容是否为空 |

示例1:判断String变量是否为空值,进而判断是否定义了这个变量
[root@localhost test]# [ -z $String  ]
[root@localhost test]# echo $?
0   # 表示未定义变量
示例2:保存当前语系的环境变量值LANG不是英语(en.US)时,则会满足逻辑测试条件并输出“非英语”的字样
[root@localhost test]# [ $LANG != "en.US"  ] && echo "非英语"
非英语
[root@localhost test]# echo $LANG
zh_CN.UTF-8

二、流程控制语句

使用linux命令,管道符,参数以及条件判断语句可以编写一些简单的shell脚本,但是这种脚本不适应与生产环境。它不能根据真实的工作需求来调整具体的执行命令,也不能根据某些条件实现自动循环执行。

例如,我们需要批量创建1000位用户,首先要判断这些用户是否已经存在;若不存在,则通过循环语句让脚本自动且依次创建他们。流程控制语句可解决这个问题。

1、if条件测试语句

作用:if条件测试语句可以让脚本根据实际情况自动执行相应的命令。

if语句分为:单分支结构双分支结构多分支结构

1.1、单分支结构

组成结构:ifthenfi关键词组成

相当于口语中的“如果……那么……”。

image

示例:判断/media/cdrom文件是否存在,若存在就结束条件判断和整个Shell脚本,反之则去创建这个目录

① 创建mkcdrom.sh脚本文件

#!/bin/bash
DIR="/media/cdrom"
if [ ! -e $DIR ]
then
  mkdir -p $DIR
fi

② 执行脚本

[root@localhost test]# ls /media
[root@localhost test]# bash mkcdrom.sh 
[root@localhost test]# ls -d /media/cdrom
/media/cdrom
[root@localhost test]# 
1.2、双分支结构

组成结构:ifthenelsefi关键词组成

相当于口语中的“如果……那么……或者……那么……”。

image

示例:验证某台主机是否在线,然后根据返回值的结果,要么显示主机在线信息,要么显示主机不在线信息。

① 创建chkhost.sh脚本文件

#!/bin/bash
ping -c 3 -i 0.2  $1 &> /dev/null
if [ $? -eq 0 ]
then
echo "主机 $1 在线"
else
echo "主机 $1 不在线"
fi

/dev/null:“空”设备,也有人称它为黑洞。任何输入到这个“设备”的数据都将被直接丢弃。最常用的用法是把不需要的输出重定向到这个文件。

② 执行脚本

[root@localhost test]# bash checkhost.sh www.baidu.com
主机 www.baidu.com 在线
[root@localhost test]# bash checkhost.sh 192.168.163.139
主机 192.168.163.139 不在线
1.3、多分支结构

组成结构:ifthenelifelsefi关键词组成

相当于口语中的“如果……那么……如果……那么……”。

image

示例:判断用户输入的分数在哪个成绩区间内,然后输出如“优秀”、“及格”、“不及格”等提示信息。

① 创建chkscore.sh脚本文件,分数大于等于85分且小于等于100分,输出“优秀”,大于等于70分且小于等于84分,输出“及格”,否则输入“不及格”

提示:Linux系统中,read是用来读取用户输入信息的命令,能够把接收到的用户输入信息赋值给后面的指定变量,-p参数用于向用户显示一定的提示信息。

#!/bin/bash
read -p "请输入你的分数(0-100):" GRADE
if [ $GRADE -ge 85 ] && [ $GRADE -le 100 ] ; then
echo "$GRADE 是优秀的"
elif [ $GRADE -ge 70 ] && [ $GRADE -le 84 ] ; then
echo "$GRADE 是及格的"
else
echo "$GRADE 是不及格的" 
fi

② 执行脚本

[root@localhost test]# bash chkscore.sh
请输入你的分数(0-100):80
80 是及格的
[root@localhost test]# bash chkscore.sh
请输入你的分数(0-100):90
90 是优秀的
[root@localhost test]# bash chkscore.sh
请输入你的分数(0-100):60
60 是不及格的

2、for条件循环语句

作用:for循环语句允许脚本一次性读取多个信息,然后逐一对信息进行操作处理。

语法格式:

image

示例1:使用for循环语句从列表文件中读取多个用户名,然后为其逐一创建用户账户并设置密码。

① 创建用户名称列表文件users.txt

zhangsan
lisi
wangwu

② 编写脚本example.sh,循环读取用户名称,使用id 用户名判断用户是否存在,不存在创建,存在输出“用户已存在”

#! /bin/bash
read -p "请输入密码:" PASSWD
for USERNAME in `cat $1`
do
  id $USERNAME &> /dev/null
  if [ $? -eq 0 ]
  then
        echo "用户已存在"
  else
        useradd $USERNAME &> /dev/null
        echo "$PASSWD" | passwd --stdin $USERNAME &> /dev/null
        if [ $? -eq 0 ]
        then
                echo "$USERNAME 用户创建成功"
        else
                echo "$USERNAME 用户创建失败"
        fi
  fi
done

③ 执行脚本,将users.txt做为参数传入创建用户

[root@localhost test]# bash example.sh users.txt 
请输入密码:1234
zhangsan 用户创建成功
lisi 用户创建成功
wangwu 用户创建成功
[root@localhost test]# tail -3 /etc/passwd
zhangsan:x:1002:1002::/home/zhangsan:/bin/bash
lisi:x:1003:1003::/home/lisi:/bin/bash
wangwu:x:1004:1004::/home/wangwu:/bin/bash

3、while条件循环语句

while条件循环语句是一种让脚本根据某些条件来重复执行命令的语句。

作用:while循环语句通过判断条件测试的真假来决定是否继续执行命令,若条件为真就继续执行,为假就结束循环。

语法格式:

image

综合示例:结合使用多分支的if条件测试语句与while条件循环语句,编写一个用来猜测数值大小的脚本guess.sh

① 编写脚本guess.sh

使用$RANDOM变量来调取出一个随机的数值(范围为0~32767),将这个随机数对1000进行取余操作,并使用expr命令取得其结果,再用这个数值与用户通过read命令输入的数值进行比较判断。这个判断语句分为三种情况,分别是判断用户输入的数值是等于、大于还是小于使用expr命令取得的数值。

提示:获得随机数语法

PRICE=$(expr $RANDOM % 1000)

  • $RANDOM:获取随机数的变量
  • %:是对随机数进行1000取余
  • expr:命令是获取取余后的结果。
#!/bin/bash
PRICE=$(expr $RANDOM % 1000)
COUNT=0 #计数变量
echo "商品实际价格为0-999之间,猜猜看是多少?"
while true
do
        read -p "请输入您猜测的价格数目:" INT
        let COUNT++ # 计数自加1
        if [ $INT -eq $PRICE ]
        then
                echo "恭喜您答对了,实际价格是 $PRICE"
                echo "您总共猜测了 $COUNT 次"
                exit 0 # 推出循环
        elif [ $INT -gt $PRICE ]
        then
                echo "太高了!"
        else
                echo "太低了!"
        fi
done

② 执行脚本

[root@localhost test]# bash guess.sh 
商品实际价格为0-999之间,猜猜看是多少?
请输入您猜测的价格数目:300
太高了!
请输入您猜测的价格数目:200
太低了!
请输入您猜测的价格数目:250
太低了!
请输入您猜测的价格数目:275
太高了!
请输入您猜测的价格数目:260
恭喜您答对了,实际价格是 260
您总共猜测了 5 次

4、case条件语句

作用:case语句是在多个范围内匹配数据,若匹配成功则执行相关命令并结束整个条件测试;而如果数据不在所列出的范围内,则会去执行星号(*)中所定义的默认命令。

语法格式:

image

示例:编写脚本checkkeys.sh,提示用户输入一个字符并将其赋值给变量KEY,然后根据变量KEY的值向用户显示其值是字母、数字还是其他字符。

① 编写checkkeys.sh脚本

#!/bin/bash
read -p "请输入一个字符,并按Enter键确认:" KEY
case "$KEY" in
[a-z]|[A-Z])
        echo "您输入的是 字母。"
        ;;
[0-9])
        echo "您输入的是 数字。"
        ;;
*)
        echo "您输入的是 空格、功能键或其他控制字符。"
esac

② 执行脚本

[root@localhost test]# bash checkkeys.sh 
请输入一个字符,并按Enter键确认:y
您输入的是 字母。
[root@localhost test]# bash checkkeys.sh 
请输入一个字符,并按Enter键确认:2
您输入的是 数字。
[root@localhost test]# bash checkkeys.sh 
请输入一个字符,并按Enter键确认:/
您输入的是 空格、功能键或其他控制字符。

作业

1、一个完整的Shell脚本应该哪些内容?

答:应该包括脚本声明、注释信息和可执行语句(即命令)。

2、分别解释Shell脚本中$0与$3变量的作用。

答:在Shell脚本中,$0代表脚本文件的名称,$3则代表该脚本在执行时接收的第三个参数。

3、if条件测试语句有几种结构,最灵活且最复杂的是哪种结构?

答:if条件测试语句包括单分支、双分支与多分支等三种结构,其中多分支结构是最灵活且最复杂的结构,其结构形式为if…then…elif…then…else…fi。

4、for条件循环语句的循环结构是什么样子的?

答:for条件循环语句的结构为“for 变量名 in 取值列表 do 命令序列 done”,如图4-20所示。

5、若在while条件循环语句中使用true作为循环条件,那么会发生什么事情?

答:因条件测试值永久为true,因此脚本中循环部分会无限地重复执行下去,直到碰到exit命令才会结束。

6、如果需要依据用户的输入参数执行不同的操作,最方便的条件测试语句是什么?

答:case条件语句。

7、完善多分支结构示例,解决输入200出现不及格的问题。

8、修改双分支语句测试主机是否在线示例,使用for循环读取主机列表文件中的主机地址,并通过ping命令测试主机是否在线。

刚学java菜鸡,永劫无间蚀月,王者荣耀王者,金铲铲小铂金,第五人格菜鸡,原神开服玩家,星穹铁道菜鸡,崩坏的菜鸡,闪耀暖暖,和平精英,LOL,CSGO,以及三A大作收集者等等。。。