Shell脚本编程

简介

  • Shell 是一个用 C 语言编写的程序,它是用户使用 Linux 的桥梁。Shell 既是一种命令语言,又是一种程序设计语言。
  • Shell 是指一种应用程序,这个应用程序提供了一个界面,用户通过这个界面访问操作系统内核的服务。(翻译官,帮你翻译命令给内核执行)

img

  • Linux 的 Shell 种类众多,常见的有:

    • Bourne Shell(/usr/bin/sh或/bin/sh)
    • Bourne Again Shell(/bin/bash)
    • C Shell(/usr/bin/csh)
    • K Shell(/usr/bin/ksh)
    • Shell for Root(/sbin/sh)
  • 程序编程风格

    • 过程式:以指令为中心,数据服务于命令
    • 对象式:以数据为中心,命令服务于数据
    • shell是一种过程式编程
  • 过程式编程

    • 顺序执行
    • 循环执行
    • 选择执行
  • 编程语言分类

    • 编译型语言
    • 解释型语言(shell是一种解释型语言)

image.png

  • 运行脚本

    • 给予执行权限,通过具体的文件路径指定文件执行
    • 直接运行解释器,将脚本作为解释器程序的参数运行
  • bash退出状态码
  • 范围是0-255
    • 脚本中一旦遇到exit命令,脚本会立即终止,终止退出状态取决于exit命令后面的数字
    • 如果未给脚本指定退出状态码,整个脚本的退出状态码取决于脚本中执行的最后一条命令的状态

变量

变量命名

  • 命名只能使用英文字母,数字和下划线,首个字符不能以数字开头。
  • 中间不能有空格,可以使用下划线(_)。
  • 不能使用标点符号。
  • 不能使用 bash 里的关键字(可用 help 命令查看保留关键字)。

声明变量

访问变量的语法形式为:${var}$var

变量名外面的花括号是可选的,加不加都行,加花括号是为了帮助解释器识别变量的边界,所以推荐加花括号。

#!/bin/bash
word="hello"
echo ${word}
# Output: hello

只读变量

使用 readonly 命令可以将变量定义为只读变量,只读变量的值不能被改变。

#!/bin/bash
rword="hello"
echo ${rword}
readonly rword
# rword="bye"  # 如果放开注释,执行时会报错

删除变量

dword="hello"  # 声明变量
echo ${dword}  # 输出变量值
# Output: hello

unset dword    # 删除变量
echo ${dword}
# Output: (空)

变量类型

  • 局部变量 - 局部变量是仅在某个脚本内部有效的变量。它们不能被其他的程序和脚本访问。
  • 环境变量 - 环境变量是对当前 shell 会话内所有的程序或脚本都可见的变量。创建它们跟创建局部变量类似,但使用的是 export 关键字,shell 脚本也可以定义环境变量。

常见的环境变量:

变量 描述
$HOME 当前用户的用户目录
$PATH 用分号分隔的目录列表,shell 会到这些目录中查找命令
$PWD 当前工作目录
$RANDOM 0 到 32767 之间的整数
$UID 数值类型,当前用户的用户 ID
$PS1 主要系统输入提示符
$PS2 次要系统输入提示符
  • 本地变量 - 生效范围仅为当前shell进程;(其他shell,当前的子sehll进程均无效)

    • 变量赋值:name = “value”
  • 位置变量 - shell 脚本中用来引用命令行参数的特殊变量。当你运行一个 shell 脚本时,可以在命令行上传递参数,这些参数可以在脚本中使用位置变量引用。

    位置变量包括以下几种:

    1. $0: 表示脚本本身的名称。
    2. $1, $2, $3, ..., $n: 分别表示第1个、第2个、第3个...第n个参数。
    3. $#: 表示传递给脚本的参数个数。
    4. $*: 表示所有参数,将所有参数当作一个整体。
    5. $@: 表示所有参数,但是每个参数都是独立的。
[root@localhost ~]# cat hello.sh
#!/bin/bash
echo "Script name: $0"
echo "First argument: $1"
echo "Second argument: $2"
echo "Total arguments: $#"
echo "All arguments: $*"
echo "All arguments (separately): $@"

[root@localhost ~]# ./hello.sh world 2023

案例:统计给出指定文件的行数

[root@localhost ~]# cat hello.sh
#!/bin/bash
linecount="$(wc -l /etc/passwd | awk -F" " '{print $1}')"
echo "This file have ${linecount} lines"

[root@localhost ~]# bash hello.sh
This file have 21 lines

字符串

shell 字符串可以用单引号 ' ',也可以用双引号 " ",也可以不用引号。

  • 单引号的特点
    • 单引号里不识别变量
    • 单引号里不能出现单独的单引号(使用转义符也不行),但可成对出现,作为字符串拼接使用。
  • 双引号的特点
    • 双引号里识别变量
    • 双引号里可以出现转义字符

综上,推荐使用双引号。

字符串的拼接

# 使用单引号拼接
name1='white'
str1='hello, '${name1}''
str2='hello, ${name1}'
echo ${str1}_${str2}
# Output:
# hello, white_hello, ${name1}

# 使用双引号拼接
name2="black"
str3="hello, "${name2}""
str4="hello, ${name2}"
echo ${str3}_${str4}
# Output:
# hello, black_hello, black

获取字符串的长度

text="12345"
echo ${#text}

# Output:
# 5

截取子字符串

${variable:start:length}

text="12345"
echo ${text:2:2}

# Output:
# 34

数组

bash 只支持一维数组。

数组下标从 0 开始,下标可以是整数或算术表达式,其值应大于或等于 0。

创建/访问数组

array_name=(value1 value2 value3 ...)
array_name=([0]=value1 [1]=value2 ...)

# 案例一
[root@localhost ~]# cat a.sh
#!/bin/bash
# 创建数组
fruits=("apple" "banana" "orange")

# 访问元素
echo "First fruit: ${fruits[0]}"
echo "All fruits: ${fruits[@]}"

[root@localhost ~]# bash a.sh
First fruit: apple
All fruits: apple banana orange

# 案例二
[root@localhost ~]# cat a.sh
nums=([0]="nls" [1]="18" [2]="teacher")
echo ${nums[1]}

[root@localhost ~]# bash a.sh
18

访问数组中所有的元素:

[root@localhost ~]# cat a.sh
nums=([0]="nls" [1]="18" [2]="teacher")
echo ${nums[*]}
echo ${nums[@]}

[root@localhost ~]# bash a.sh
nls 18 teacher
nls 18 teacher

获取数组的长度

[root@localhost ~]# cat a.sh
nums=([0]="nls" [1]="18" [2]="teacher")
echo "数组元素个数为: ${#nums[*]}"

[root@localhost ~]# bash a.sh
数组元素个数为: 3

删除元素

unset命令来从数组中删除一个元素:

[root@localhost ~]# cat a.sh
nums=([0]="nls" [1]="18" [2]="teacher")
echo "数组元素个数为: ${#nums[*]}"
unset nums[0]
echo "数组元素个数为: ${#nums[*]}"

[root@localhost ~]# bash a.sh
数组元素个数为: 3
数组元素个数为: 2

运算符

算数运算符

下表列出了常用的算术运算符,假定变量 x 为 10,变量 y 为 20:

运算符 说明 举例
+ 加法 expr $x + $y 结果为 30。
- 减法 expr $x - $y 结果为 -10。
* 乘法 expr $x * $y 结果为 200。
/ 除法 expr $y / $x 结果为 2。
% 取余 expr $y % $x 结果为 0。
= 赋值 x=$y 将把变量 y 的值赋给 x。
== 相等。用于比较两个数字,相同则返回 true。 [ $x == $y ] 返回 false。
!= 不相等。用于比较两个数字,不相同则返回 true。 [ $x != $y ] 返回 true。

注意:条件表达式要放在方括号之间,并且要有空格,例如: [$x==$y] 是错误的,必须写成 [ $x == $y ]

示例:

  • expr本身是一个命令,可以直接进行运算
x=10
y=20

echo "x=${x}, y=${y}"

val=`expr ${x} + ${y}`
echo "${x} + ${y} = $val"

val=`expr ${x} - ${y}`
echo "${x} - ${y} = $val"

val=`expr ${x} \* ${y}`
echo "${x} * ${y} = $val"

val=`expr ${y} / ${x}`
echo "${y} / ${x} = $val"

val=`expr ${y} % ${x}`
echo "${y} % ${x} = $val"

if [[ ${x} == ${y} ]]
then
  echo "${x} = ${y}"
fi
if [[ ${x} != ${y} ]]
then
  echo "${x} != ${y}"
fi

#  Execute: ./operator-demo.sh
#  Output:
#  x=10, y=20
#  10 + 20 = 30
#  10 - 20 = -10
#  10 * 20 = 200
#  20 / 10 = 2
#  20 % 10 = 0
#  10 != 20

案例一:计算ID之和

计算/etc/passwd文件中第10个用户和第15个用户的ID之和

[root@localhost ~]# cat id.sh
#!/bin/bash
# userid1=$(cat /etc/passwd | sed -n '10p'| awk -F: '{print $3}')
# userid2=$(cat /etc/passwd | sed -n '15p'| awk -F: '{print $3}')
userid1=$(awk -F: '{if (NR==10) print $3}' /etc/passwd)
userid2=$(awk -F: '{if (NR==15) print $3}' /etc/passwd)

userid_sum=$[$userid1 + $userid2]
echo $userid_sum

# Execute:
[root@localhost ~]# bash id.sh
92

案例二:统计文件数量

统计/etc/,/var/,/usr/目录下有多少目录和文件

[root@localhost ~]# cat file.sh
#!/bin/bash
sum_etc=$(find /etc | wc -l)
sum_var=$(find /var | wc -l)
sum_usr=$(find /usr | wc -l)

sum=$[$sum_etc + $sum_var + $sum_usr]
echo $sum

# Execute:
[root@localhost ~]# bash file.sh
35686

关系运算符

关系运算符只支持数字,不支持字符串,除非字符串的值是数字。

下表列出了常用的关系运算符,假定变量 x 为 10,变量 y 为 20:

运算符 说明 举例
-eq 检测两个数是否相等,相等返回 true。 [ $a -eq $b ]返回 false。
-ne 检测两个数是否相等,不相等返回 true。 [ $a -ne $b ] 返回 true。
-gt 检测左边的数是否大于右边的,如果是,则返回 true。 [ $a -gt $b ] 返回 false。
-lt 检测左边的数是否小于右边的,如果是,则返回 true。 [ $a -lt $b ] 返回 true。
-ge 检测左边的数是否大于等于右边的,如果是,则返回 true。 [ $a -ge $b ] 返回 false。
-le 检测左边的数是否小于等于右边的,如果是,则返回 true。 [ $a -le $b ]返回 true。

示例:

x=10
y=20

echo "x=${x}, y=${y}"

if [[ ${x} -eq ${y} ]]; then
   echo "${x} -eq ${y} : x 等于 y"
else
   echo "${x} -eq ${y}: x 不等于 y"
fi

if [[ ${x} -ne ${y} ]]; then
   echo "${x} -ne ${y}: x 不等于 y"
else
   echo "${x} -ne ${y}: x 等于 y"
fi

if [[ ${x} -gt ${y} ]]; then
   echo "${x} -gt ${y}: x 大于 y"
else
   echo "${x} -gt ${y}: x 不大于 y"
fi

if [[ ${x} -lt ${y} ]]; then
   echo "${x} -lt ${y}: x 小于 y"
else
   echo "${x} -lt ${y}: x 不小于 y"
fi

if [[ ${x} -ge ${y} ]]; then
   echo "${x} -ge ${y}: x 大于或等于 y"
else
   echo "${x} -ge ${y}: x 小于 y"
fi

if [[ ${x} -le ${y} ]]; then
   echo "${x} -le ${y}: x 小于或等于 y"
else
   echo "${x} -le ${y}: x 大于 y"
fi

#  Execute: ./operator-demo2.sh
#  Output:
#  x=10, y=20
#  10 -eq 20: x 不等于 y
#  10 -ne 20: x 不等于 y
#  10 -gt 20: x 不大于 y
#  10 -lt 20: x 小于 y
#  10 -ge 20: x 小于 y
#  10 -le 20: x 小于或等于 y

案例:猜数字小游戏

[root@localhost ~]# vim guess.sh
#!/bin/bash
num2=66
while true
do
    read -p "请输入你要猜的数字:" num1
    if [ $num1 -gt $num2 ];then
        echo "你猜大了"
    elif [ $num1 -lt $num2 ];then
        echo "你猜小了"
    else
        echo "你猜对了"
        break
    fi
done

# Execute:
[root@localhost ~]# bash guess.sh
请输入你要猜的数字:60
你猜小了
请输入你要猜的数字:66
你猜对了

字符串运算符

下表列出了常用的字符串运算符,假定变量 a 为 "abc",变量 b 为 "efg":

运算符 说明 举例
= 检测两个字符串是否相等,相等返回 true。 [ $a = $b ] 返回 false。
!= 检测两个字符串是否相等,不相等返回 true。 [ $a != $b ] 返回 true。
-z 检测字符串长度是否为 0,为 0 返回 true。 [ -z $a ] 返回 false。
-n 检测字符串长度是否为 0,不为 0 返回 true。 [ -n $a ] 返回 true。
str 检测字符串是否为空,不为空返回 true。 [ $a ] 返回 true。

示例:

x="abc"
y="xyz"


echo "x=${x}, y=${y}"

if [[ ${x} = ${y} ]]; then
   echo "${x} = ${y} : x 等于 y"
else
   echo "${x} = ${y}: x 不等于 y"
fi

if [[ ${x} != ${y} ]]; then
   echo "${x} != ${y} : x 不等于 y"
else
   echo "${x} != ${y}: x 等于 y"
fi

if [[ -z ${x} ]]; then
   echo "-z ${x} : 字符串长度为 0"
else
   echo "-z ${x} : 字符串长度不为 0"
fi

if [[ -n "${x}" ]]; then
   echo "-n ${x} : 字符串长度不为 0"
else
   echo "-n ${x} : 字符串长度为 0"
fi

if [[ ${x} ]]; then
   echo "${x} : 字符串不为空"
else
   echo "${x} : 字符串为空"
fi

#  Execute: ./operator-demo5.sh
#  Output:
#  x=abc, y=xyz
#  abc = xyz: x 不等于 y
#  abc != xyz : x 不等于 y
#  -z abc : 字符串长度不为 0
#  -n abc : 字符串长度不为 0
#  abc : 字符串不为空

逻辑运算符

以下介绍 Shell 的逻辑运算符,假定变量 x 为 10,变量 y 为 20:

运算符 说明 举例
&& 逻辑的 AND [[ ${x} -lt 100 && ${y} -gt 100 ]] 返回 false
` ` 逻辑的 OR [[ ${x} -lt 100 && ${y} -gt 100 ]]返回 true

示例:

x=10
y=20

echo "x=${x}, y=${y}"

if [[ ${x} -lt 100 && ${y} -gt 100 ]]
then
   echo "${x} -lt 100 && ${y} -gt 100 返回 true"
else
   echo "${x} -lt 100 && ${y} -gt 100 返回 false"
fi

if [[ ${x} -lt 100 || ${y} -gt 100 ]]
then
   echo "${x} -lt 100 || ${y} -gt 100 返回 true"
else
   echo "${x} -lt 100 || ${y} -gt 100 返回 false"
fi

#  Execute: ./operator-demo4.sh
#  Output:
#  x=10, y=20
#  10 -lt 100 && 20 -gt 100 返回 false
#  10 -lt 100 || 20 -gt 100 返回 true

布尔运算符

下表列出了常用的布尔运算符,假定变量 x 为 10,变量 y 为 20:

运算符 说明 举例
! 非运算,表达式为 true 则返回 false,否则返回 true。 [ ! false ] 返回 true。
-o 或运算,有一个表达式为 true 则返回 true。 [ $a -lt 20 -o $b -gt 100 ] 返回 true。
-a 与运算,两个表达式都为 true 才返回 true。 [ $a -lt 20 -a $b -gt 100 ] 返回 false。

示例:

x=10
y=20

echo "x=${x}, y=${y}"

if [[ ${x} != ${y} ]]; then
   echo "${x} != ${y} : x 不等于 y"
else
   echo "${x} != ${y}: x 等于 y"
fi

if [[ ${x} -lt 100 && ${y} -gt 15 ]]; then
   echo "${x} 小于 100 且 ${y} 大于 15 : 返回 true"
else
   echo "${x} 小于 100 且 ${y} 大于 15 : 返回 false"
fi

if [[ ${x} -lt 100 || ${y} -gt 100 ]]; then
   echo "${x} 小于 100 或 ${y} 大于 100 : 返回 true"
else
   echo "${x} 小于 100 或 ${y} 大于 100 : 返回 false"
fi

if [[ ${x} -lt 5 || ${y} -gt 100 ]]; then
   echo "${x} 小于 5 或 ${y} 大于 100 : 返回 true"
else
   echo "${x} 小于 5 或 ${y} 大于 100 : 返回 false"
fi

#  Execute: ./operator-demo3.sh
#  Output:
#  x=10, y=20
#  10 != 20 : x 不等于 y
#  10 小于 100 且 20 大于 15 : 返回 true
#  10 小于 100 或 20 大于 100 : 返回 true
#  10 小于 5 或 20 大于 100 : 返回 false

文件测试运算符

文件测试运算符用于检测 Unix 文件的各种属性。

属性检测描述如下:

操作符 说明 举例
-b file 检测文件是否是块设备文件,如果是,则返回 true。 [ -b $file ] 返回 false。
-c file 检测文件是否是字符设备文件,如果是,则返回 true。 [ -c $file ] 返回 false。
-d file 检测文件是否是目录,如果是,则返回 true。 [ -d $file ] 返回 false。
-f file 检测文件是否是普通文件(既不是目录,也不是设备文件),如果是,则返回 true。 [ -f $file ] 返回 true。
-g file 检测文件是否设置了 SGID 位,如果是,则返回 true。 [ -g $file ] 返回 false。
-k file 检测文件是否设置了粘着位(Sticky Bit),如果是,则返回 true。 [ -k $file ]返回 false。
-p file 检测文件是否是有名管道,如果是,则返回 true。 [ -p $file ] 返回 false。
-u file 检测文件是否设置了 SUID 位,如果是,则返回 true。 [ -u $file ] 返回 false。
-r file 检测文件是否可读,如果是,则返回 true。 [ -r $file ] 返回 true。
-w file 检测文件是否可写,如果是,则返回 true。 [ -w $file ] 返回 true。
-x file 检测文件是否可执行,如果是,则返回 true。 [ -x $file ] 返回 true。
-s file 检测文件是否为空(文件大小是否大于 0),不为空返回 true。 [ -s $file ] 返回 true。
-e file 检测文件(包括目录)是否存在,如果是,则返回 true。 [ -e $file ] 返回 true。

⌨️ 『示例源码』 operator-demo6.sh

file="/etc/hosts"

if [[ -r ${file} ]]; then
   echo "${file} 文件可读"
else
   echo "${file} 文件不可读"
fi
if [[ -w ${file} ]]; then
   echo "${file} 文件可写"
else
   echo "${file} 文件不可写"
fi
if [[ -x ${file} ]]; then
   echo "${file} 文件可执行"
else
   echo "${file} 文件不可执行"
fi
if [[ -f ${file} ]]; then
   echo "${file} 文件为普通文件"
else
   echo "${file} 文件为特殊文件"
fi
if [[ -d ${file} ]]; then
   echo "${file} 文件是个目录"
else
   echo "${file} 文件不是个目录"
fi
if [[ -s ${file} ]]; then
   echo "${file} 文件不为空"
else
   echo "${file} 文件为空"
fi
if [[ -e ${file} ]]; then
   echo "${file} 文件存在"
else
   echo "${file} 文件不存在"
fi

#  Execute: ./operator-demo6.sh
#  Output:(根据文件的实际情况,输出结果可能不同)
#  /etc/hosts 文件可读
#  /etc/hosts 文件可写
#  /etc/hosts 文件不可执行
#  /etc/hosts 文件为普通文件
#  /etc/hosts 文件不是个目录
#  /etc/hosts 文件不为空
#  /etc/hosts 文件存在

用户交互read

常用选项

选项 描述
-p 在读取输入之前显示提示信息
-n 限制输入的字符数
-s 隐藏用户输入
-a 将输入存储到数组变量中
-d 指定用于终止输入的分隔符
-t 设置超时时间(以秒为单位)
-e 允许使用 Readline 编辑键
-i 设置默认值

示例:

#!/bin/bash
read -p "input you name:" name
echo $name

# Output:
nls

案例:计算器

#!/bin/bash

echo "Enter the first number:"
read num1
echo "Enter the second number:"
read num2

echo "The sum is: $((num1 + num2))"
echo "The difference is: $((num1 - num2))"
echo "The product is: $((num1 * num2))"
echo "The quotient is: $((num1 / num2))"

# Output:
[root@localhost ~]# bash read.sh
Enter the first number:
10
Enter the second number:
10
The sum is: 20
The difference is: 0
The product is: 100
The quotient is: 1

控制语句

条件语句

跟其它程序设计语言一样,Bash 中的条件语句让我们可以决定一个操作是否被执行。结果取决于一个包在[[ ]]里的表达式。

[[ ]]sh中是[ ])包起来的表达式被称作 检测命令基元。这些表达式帮助我们检测一个条件的结果

  1. if 语句

if在使用上跟其它语言相同。如果中括号里的表达式为真,那么thenfi之间的代码会被执行。fi标志着条件代码块的结束。

# 写成一行
if [[ 1 -eq 1 ]]; then echo "1 -eq 1 result is: true"; fi
# Output: 1 -eq 1 result is: true

# 写成多行
if [[ "abc" -eq "abc" ]]
then
  echo ""abc" -eq "abc" result is: true"
fi
# Output: abc -eq abc result is: true
  1. if else 语句

同样,我们可以使用if..else语句,例如:

if [[ 2 -ne 1 ]]; then
  echo "true"
else
  echo "false"
fi
# Output: true
  1. if elif else 语句

有些时候,if..else不能满足我们的要求。别忘了if..elif..else,使用起来也很方便。

x=10
y=20
if [[ ${x} > ${y} ]]; then
   echo "${x} > ${y}"
elif [[ ${x} < ${y} ]]; then
   echo "${x} < ${y}"
else
   echo "${x} = ${y}"
fi
# Output: 10 < 20

循环语句

循环其实不足为奇。跟其它程序设计语言一样,bash 中的循环也是只要控制条件为真就一直迭代执行的代码块。Bash 中有四种循环:forwhileuntilselect

for循环

for与 C 语言中非常像。看起来是这样:

for arg in elem1 elem2 ... elemN
do
  ### 语句
done

在每次循环的过程中,arg依次被赋值为从elem1elemN。这些值还可以是通配符或者大括号扩展

当然,我们还可以把for循环写在一行,但这要求do之前要有一个分号,就像下面这样:

for i in {1..5}; do echo $i; done

还有,如果你觉得for..in..do对你来说有点奇怪,那么你也可以像 C 语言那样使用for,比如:

for (( i = 0; i < 10; i++ )); do
  echo $i
done

当我们想对一个目录下的所有文件做同样的操作时,for就很方便了。举个例子,如果我们想把所有的.bash文件移动到script文件夹中,并给它们可执行权限,我们的脚本可以这样写:

DIR=/home/zp
for FILE in ${DIR}/*.sh; do
  mv "$FILE" "${DIR}/scripts"
done
# 将 /home/zp 目录下所有 sh 文件拷贝到 /home/zp/scripts

案例一:创建用户

创建用户user1‐user10家目录,并且在user1‐10家目录下创建1.txt‐10.txt

[root@localhost ~]# cat adduser.sh
#!/bin/bash
for i in {1..10}
do
    mkdir /home/user$i
    for j in $(seq 10)
    do
       touch /home/user$i/$j.txt
    done
done

# Output:
[root@localhost ~]# bash adduser.sh
[root@localhost ~]# ls /home/
user01  user10  user3  user5  user7  user9
user1   user2   user4  user6  user8
[root@localhost ~]# ls /home/user1
10.txt  2.txt  4.txt  6.txt  8.txt
1.txt   3.txt  5.txt  7.txt  9.txt

案例二:检查磁盘占用

列出/var/目录下各个子目录占用磁盘大小

[root@localhost ~]# cat size.sh
#!/bin/bash
for i in `ls /var/`
do
    path="/var/$i"
    if [ -d $path ];then
        du -sh $path
    fi
done

# Output:
[root@localhost ~]# bash size.sh
0       /var/adm
654M    /var/cache
0       /var/crash
8.0K    /var/db
0       /var/empty
0       /var/games
0       /var/gopher
0       /var/kerberos
54M     /var/lib
0       /var/local
0       /var/lock
3.2M    /var/log
0       /var/mail
0       /var/nis
0       /var/opt
0       /var/preserve
0       /var/run
16K     /var/spool
0       /var/tmp
0       /var/www
0       /var/yp

案例三:测试连通性

批量测试地址是否在线

[root@localhost ~]# cat ping.sh
#!/bin/bash
for i in {1..10}
do
    ping -c 2 192.168.88.$i &> /dev/null
    if [ $? -eq 0 ];then
        echo 192.168.88.$i >> /root/host.txt
    fi
done

# Output:
[root@localhost ~]# cat host.txt
192.168.88.1
192.168.88.2
192.168.88.10

while循环

while循环检测一个条件,只要这个条件为 ,就执行一段命令。被检测的条件跟if..then中使用的基元并无二异。因此一个while循环看起来会是这样:

while 循环条件
do
  ### 语句
done

案例一:数字累加

计算1+2+..10的总和

[root@localhost ~]# cat sum.sh
#!/bin/bash
i=1
sum=0
while [ $i -lt 10 ]
do
    let sum+=$i
    let i++
done
echo $sum

# Output:
[root@localhost ~]# bash sum.sh
45

案例二:猜数字小游戏

加上随机数

[root@localhost ~]# cat guess.sh
#!/bin/bash
num2=$((RANDOM%100+1))
while true
do
    read -p "请输入你要猜的数字:" num1
    if [ $num1 -gt $num2 ];then
        echo "你猜大了"
    elif [ $num1 -lt $num2 ];then
        echo "你猜小了"
    else
        echo "你猜对了"
        break
    fi
done

# Output:
[root@localhost ~]# bash guess.sh
请输入你要猜的数字:50
你猜小了
请输入你要猜的数字:70
你猜小了
请输入你要猜的数字:90
你猜大了
请输入你要猜的数字:80
你猜大了

until循环

until循环跟while循环正好相反。它跟while一样也需要检测一个测试条件,但不同的是,只要该条件为 就一直执行循环:

until 条件测试
do
    ##循环体
done

示例:

[root@localhost ~]# cat until.sh
x=0
until [ ${x} -ge 5 ]; do
  echo ${x}
  x=`expr ${x} + 1`
done

# Output
[root@localhost ~]# bash until.sh
0
1
2
3
4

退出循环

breakcontinue

如果想提前结束一个循环或跳过某次循环执行,可以使用 shell 的breakcontinue语句来实现。它们可以在任何循环中使用。

break语句用来提前结束当前循环。

continue语句用来跳过某次迭代。

示例:

# 查找 10 以内第一个能整除 2 和 3 的正整数
i=1
while [[ ${i} -lt 10 ]]; do
  if [[ $((i % 3)) -eq 0 ]] && [[ $((i % 2)) -eq 0 ]]; then
    echo ${i}
    break;
  fi
  i=`expr ${i} + 1`
done
# Output: 6

示例:

# 打印10以内的奇数
for (( i = 0; i < 10; i ++ )); do
  if [[ $((i % 2)) -eq 0 ]]; then
    continue;
  fi
  echo ${i}
done
#  Output:
#  1
#  3
#  5
#  7
#  9

函数

函数定义

bash 函数定义语法如下:

[ function ] funname [()] {
    action;
    [return int;]
}
function FUNNAME(){
函数体
返回值
}
FUNNME #调用函数

💡 说明:

  1. 函数定义时,function 关键字可有可无。
  2. 函数返回值 - return 返回函数返回值,返回值类型只能为整数(0-255)。如果不加 return 语句,shell 默认将以最后一条命令的运行结果,作为函数返回值。
  3. 函数返回值在调用该函数后通过 $? 来获得。
  4. 所有函数在使用前必须定义。这意味着必须将函数放在脚本开始部分,直至 shell 解释器首次发现它时,才可以使用。调用函数仅使用其函数名即可。

示例:

[root@localhost ~]# cat func.sh
#!/bin/bash
func(){
    echo "这是我的第一个函数"
}

echo "------函数执行之前-------"
func
echo "------函数执行之前-------"

# Output:
[root@localhost ~]# bash func.sh
------函数执行之前-------
这是我的第一个函数
------函数执行之前-------

返回值

示例:

func(){
    echo "这个函数会对输入的两个数字进行相加运算..."
    echo "输入第一个数字: "
    read aNum
    echo "输入第二个数字: "
    read anotherNum
    echo "两个数字分别为 $aNum$anotherNum !"
    return $(($aNum+$anotherNum))
}
func
echo "输入的两个数字之和为 $? !"
#可以使用$?来获取返回值

函数参数

位置参数是在调用一个函数并传给它参数时创建的变量

变量 描述
$0 脚本名称
$1 … $9 第 1 个到第 9 个参数列表
${10} … ${N} 第 10 个到 N 个参数列表
$* or $@ 除了$0外的所有位置参数
$# 不包括$0在内的位置参数的个数
$FUNCNAME 函数名称(仅在函数内部有值)

示例:

#!/bin/bash

x=0
if [[ -n $1 ]]; then
  echo "第一个参数为:$1"
  x=$1
else
  echo "第一个参数为空"
fi

y=0
if [[ -n $2 ]]; then
  echo "第二个参数为:$2"
  y=$2
else
  echo "第二个参数为空"
fi

paramsFunction(){
  echo "函数第一个入参:$1"
  echo "函数第二个入参:$2"
}
paramsFunction ${x} ${y}

执行结果:

[root@localhost ~]# vim func1.sh
[root@localhost ~]# bash func1.sh
第一个参数为空
第二个参数为空
函数第一个入参:0
函数第二个入参:0
[root@localhost ~]# bash func1.sh 10 20
第一个参数为:10
第二个参数为:20
函数第一个入参:10
函数第二个入参:20

函数处理参数

另外,还有几个特殊字符用来处理参数:

参数处理 说明
$# 返回参数个数
$* 返回所有参数
`$ 参数处理
-------- ------------------------------------------------

| $! | 后台运行的最后一个进程的 ID 号 | | $@ | 返回所有参数 | | $- | 返回 Shell 使用的当前选项,与 set 命令功能相同。 | | $? | 函数返回值 |

runner() {
  return 0
}

name=zp
paramsFunction(){
  echo "函数第一个入参:$1"
  echo "函数第二个入参:$2"
  echo "传递到脚本的参数个数:$#"
  echo "所有参数:"
  printf "+ %s\n" "$*"
  echo "脚本运行的当前进程 ID 号:$$"
  echo "后台运行的最后一个进程的 ID 号:$!"
  echo "所有参数:"
  printf "+ %s\n" "$@"
  echo "Shell 使用的当前选项:$-"
  runner
  echo "runner 函数的返回值:$?"
}
paramsFunction 1 "abc" "hello, \"zp\""
#  Output:
#  函数第一个入参:1
#  函数第二个入参:abc
#  传递到脚本的参数个数:3
#  所有参数:
#  + 1 abc hello, "zp"
#  脚本运行的当前进程 ID 号:26400
#  后台运行的最后一个进程的 ID 号:
#  所有参数:
#  + 1
#  + abc
#  + hello, "zp"
#  Shell 使用的当前选项:hB
#  runner 函数的返回值:0

实际案例

案例一:开机显示系统信息脚本

[root@localhost ~]# cat os.sh
#!/bin/bash
yum install -y net-tools &> /dev/null
wangka=`ip a | grep ens | head -1 | cut -d: -f2`
System=$(hostnamectl | grep System | awk '{print $3,$4,$5}')
Kernel=$(hostnamectl | grep Kernel | awk -F: '{print $2}')
Virtualization=$(hostnamectl | grep Virtualization| awk '{print $2}')
Statichostname=$(hostnamectl | grep Static|awk -F: '{print $2}')
Ens32=$(ifconfig $wangka | awk 'NR==2 {print $2}')
Lo=$(ifconfig lo0 | awk 'NR==2 {print $2}')
NetworkIp=$(curl -s icanhazip.com)
echo "当前系统版本是:$System"
echo "当前系统内核是:$Kernel"
echo "当前虚拟平台是:$Virtualization"
echo "当前主机名是:$Statichostname"
echo "当前网卡$wangka的地址是:$Ens32"
echo "当前lo0接口的地址是:$Lo"
echo "当前公网地址是:$NetworkIp"

# Output:
[root@localhost ~]# bash os.sh
当前系统版本是:CentOS Linux 7
当前系统内核是: Linux 3.10.0-957.el7.x86_64
当前虚拟平台是:vmware
当前主机名是: localhost
当前网卡 ens33的地址是:192.168.88.10
当前lo0接口的地址是:127.0.0.1
当前公网地址是:153.101.189.87

案例二:监控httpd进程

需求:

1.每隔10s监控httpd的进程数,若进程数大于等于500,则自动重启Apache服务,并检测服务是否重启成功

2.若未成功则需要再次启动,若重启5次依旧没有成功,则向管理员发送告警邮件(使用echo输出已发送即可),并退出检测

3.如果启动成功,则等待1分钟后再次检测httpd进程数,若进程数正常,则恢复正常检测(10s一次),否则放弃重启并向管理员发送告警邮件,并退出检测

[root@localhost ~]# cat httpd.sh
#!/bin/bash
function check_httpd_process_number() {
process_num=`ps -ef | grep httpd| wc -l`

if [ $process_num -gt 50 ];then
    systemctl restart httpd &> /dev/null
    # 重启五次httpd确保服务启动
    systemctl status httpd &> /dev/null
    if [ $? -ne 0 ];then
        num_restart_httpd=0
        while true;do
            let num_restart_httpd++
            systemctl restart httpd &> /dev/null
            systemctl status httpd &> /dev/null
            [ $? -eq 0 ]  && break
            [ $num_restart_httpd -eq 6 ] && break
        done
    fi

    # 判断重启服务的结果
    systemctl status httpd &> /dev/null
    [ $? -ne 0 ] && echo "apache未正常重启,已发送邮件给管理员" && return                                                                                    1
    sleep 60
        return 0

    # 再次判断进程是否正常
    process_num=`ps -ef | grep httpd| wc -l`
    if [ $process_num -gt 50 ] ;then
        echo "apache经过重启进程数依然大于50"
        return 1
    else
        return 0
    fi

else
    echo "进程数小于50"
    sleep 3
    return 0
fi
}

# 每十秒钟执行一次函数,检查进程是否正常
while true;do
check_httpd_process_number
[ $? -eq 1 ] && exit
done

# Output:
[root@localhost ~]# bash http.sh
进程数小于50
进程数小于50
进程数小于50
进程数小于50

# 复制窗口进行压力测试
[root@localhost ~]# for i in {1..10}; do ab -c $((10000/$i)) -n 2000 http://127.0.0.1/ & done

案例三:统计文件

统计两个目录下的相同文件,以及不同文件

#!/bin/bash
# server1的文件在/test/目录中,server2的文件在/root/demo中,通过md5值来判断文件一致性,最终输出相同文件以及各自的不同文件
#定义两个数组的索引
point1=0
point2=0
echo "/test/的文件:"
# 将server1上的文件的散列值记录到数组当中
for i in `ls /root/demo`;do
    md5=`md5sum /root/demo/$i | awk '{print $1}'`
    arrar1[$point1]=$md5:$i
    echo ${arrar1[$point1]}
    let point1++
done
echo "/root/demo的文件:"
# 将server2上的文件的散列值记录到数组当中
for i in `ls /test`;do
    md5=`md5sum /test/$i | awk '{print $1}'`
    arrar2[$point2]=$md5:$i
    echo ${arrar2[$point2]}
    let point2++
done

# 找出相同文件以及server1上的独立文件,server1的每个文件都和server2上进行比较
echo "-------------------------------"
for i in ${arrar1[@]};do
    for j in ${arrar2[@]};do
        temp_flag=0     #定义一个标志位,表示没找到相同的文件
        server1_md5=`echo $i | awk -F: '{print $1}'`
        server2_md5=`echo $j | awk -F: '{print $1}'`
        server1_filename=`echo $i | awk -F: '{print $2}'`
        server2_filename=`echo $j | awk -F: '{print $2}'`
        if [ $server1_md5 == $server2_md5 ];then
            echo -e "两边共同文件\t\t\t$server1_filename"
            temp_flag=1    #找到了相同的文件
            break
        fi
    done
    if [ $temp_flag -eq 0 ];then
        echo -e "server1不同文件\t\t\t$i"
    fi
done

# 找出server2上的独立文件
for i in ${arrar2[@]};do
    for j in ${arrar1[@]};do
        temp_flag=0
        server1_md5=`echo $i | awk -F: '{print $1}'`
        server2_md5=`echo $j | awk -F: '{print $1}'`
        server1_filename=`echo $i | awk -F: '{print $2}'`
        server2_filename=`echo $j | awk -F: '{print $2}'`
        if [ $server1_md5 == $server2_md5 ];then
            temp_flag=1
            break
        fi
    done
    if [ $temp_flag -eq 0 ];then
        echo -e "server2不同文件\t\t\t$i"
    fi
done

练习:基于文件的用户登录注册功能

用户名和密码保存在文件中,格式为username:password

学前沿IT,到英格科技!本文发布时间: 2024-08-10 08:56:25

results matching ""

    No results matching ""