Shell脚本编程

Shell脚本编程

简介

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

在这里插入图片描述

  • 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是一种解释型语言)

在这里插入图片描述

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

变量

变量命名

  • 命名只能使用英文字母,数字和下划线,首字母不能以数字开头
  • 中间不能够有特殊字符,可以使用_下划线
  • 不能使用标点符号
  • 不能使用bash中的关键字
有效命名:
RUNOOB
LD_LIBRARY_PATH
_var
var2
无效命名:
?var=123
user*name=runoob
语句给变量赋值
for file in `ls /etc`for file in $(ls /etc)

使用变量

定义变量:
your_name="eagles"
使用变量:
echo $your_name
echo ${your_name}
建议使用{}号作边界
for skill in Ada Coffe Action Java; do
echo "I am good at ${skill}Script"
done
如果使用$skillScript,则将会输出空值

只读变量

#!/bin/bash
myUrl="www.xxx.com"
readonly myUrl
myUrl="www.baidu.com"
执行脚本后,显示只读变量无法修改 

删除变量

#!/bin/sh
myurl="www.baidu.com"
unset myurl
echo $myurl

变量种类

  • 本地变量:生效范围仅为当前shell进程;(其他shell,当前的子shell进程均无效)
    • 变量赋值:name = “value”
  • 环境变量:生效范围为当前shell进程及子进程
    • 变量声明1:export name = “value”
    • 变量声明2:declare -x name = “value”
    • bash中有许多内建的变量环境:SHELL、PATH等等
  • 局部变量:生效范围为当前shell进程中某代码片段(通常指函数)
  • 位置变量:2…来表示,让脚本在脚本代码中调用通过命令行传递给它的参数;
  • 特殊变量:?0 * @ #
$1,$2,…:对应调用第1,第2等参数
$0:命令本身
$*:传递给脚本的所有参数(把所有参数当作整体)
$@:传递给脚本的所有参数
$#:传递给脚本的参数的个数

案例1:
myecho.sh
#!/bin/bash
echo "命令本身是:$0"
echo "第一个参数是:$1"
echo "第二个参数是:$2"
echo "一共有$#个参数"
echo "所有参数是:$@"

案例2:判断所给文件的行数
linecount.sh
#!/bin/bash
linecount="$(wc -l $1|cut -d' ' -f1)"
echo "This file have ${linecount} lines"

数组

  • 语法格式
语法格式:array_name=(value1 ... valuen)
示例:
my_array=(A B C D)   #以空格间隔
array_name[0]=value0
array_name[1]=value1
array_name[2]=value2
  • 读取数据
读取数组:${array_name[index]}
获取数组中的所有元素:
my_array[0]=A
my_array[1]=B
my_array[2]=C
my_array[3]=D
echo "数组的元素为: ${my_array[*]}"
echo "数组的元素为: ${my_array[@]}"
  • 读取数组的长度
获取数组的长度:
my_array[0]=A
my_array[1]=B
my_array[2]=C
my_array[3]=D
echo "数组元素个数为: ${#my_array[*]}"
echo "数组元素个数为: ${#my_array[@]}"

算数运算

  • 运算符
+ ‐ * / % ** ...
增强赋值:
+=,‐=,*=,/=,%=
乘法符号有些场景中需要转义 : *\
bash有内建随机数生成器:$RANDOM
	获取1-100的随机数:echo "[$RANDOM%100+1]"
  • 完成算数运算
let var(变量名) = 算数表达式
var=$[算数表达式]
var=$((算数表达式))
var=$(expr arg1 arg2 arg3 ...)     var=$(expr 1+2)
expr本事是一个命令,可以直接进行运算
[root@centos73 ~]# expr 1+2
1+2
[root@centos73 ~]# expr 1 + 2注意要空格才可以进行运算。乘法符号有些场景中需要转义,如*。也就是expr这个命令后面跟的是3个参数
3
  • 练习题

练习1:计算/etc/passwd文件中第10个用户的第20个用户的ID之和

x=`cat /etc/passwd | awk -F":" '{print $3}'|awk '{if(NR==10)print $1}'`
y=`cat /etc/passwd | awk -F":" '{print $3}'|awk '{if(NR==19)print $1}'`
let sum=$x+$y
echo $sum

练习2:传递两个文件路径参数给脚本,计算这两个文件之中所有空白行之和

a=`egrep -c "^$" /etc/a`
b=`egrep -c "^$" /etc/b`
let sum=$a+$b
echo $sum

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

a=`find /etc | wc -l`
b=`find /var | wc -l`
c=`find /usr | wc -l`
let sum=$a+$b+$c
echo $sum

条件测试

测试命令:test EXPERSSION

num1=100
num2=100
if test $[num1] -eq $[num2]
then 
	echo "两个数相等!"
else
	echo "两个数不相等!"
fi

数值测试

-gt 即 >

-ge 即 >=

-eq 即 =

-ne 即 !=

-lt 即 <

-le 即 <=

练习题,比较两个数的大小

[root@localhost ~]# cat diff.sh
#!/bin/bash
read -p "请输入两个整数" num1 num2
if [ $num1 -gt $num2 ];then
	echo "$num1 > $num2"
elif [ $num1 -lt $num2 ];then
	echo "$num1 < $num2"
else
	echo "$num1 = $num2"
fi

字符串测试

==:是否等于
>:是否大于
<:是否小于
!=:是否不等于
=~:左侧字符串是否能够被右侧的PATTERN所匹配
Note:此表达式一般用于[[ ]]中
‐z “STRING”:测试字符串是否为空,空则为真,不空则为假
‐n “STRING”:测试字符串是否不空,不空则为真,空则为假

文件测试

简单的存在性测试:
‐a FILE :文件存在性测试,存在为真,否则为假

存在性及类型测试:
‐b FLIE:是否存在且为块设备文件;
‐c FILE:是否存在且为字符设备文件;
‐d FILE:是否存在且为目录文件;
‐f FILE:是否存在且为普通文件;
‐h FILE 或 ‐L FILE : 存在且为符号链接文件;
‐p FIEL :是否存在且为命名管道文件;
‐S FILE:是否存在且为套接文件;

文件权限测试:
‐r FILE:是否存在且可读
‐w FILE:是否存在且可写
‐x FILE:是否存在可执行

文件特殊权限测试:
‐u FILE:是否存在且拥有suid权限;
‐g FILE:是否存在且拥有sgid权限;
‐k FILE:是否存在且拥有sticky权限;

文件大小测试:
‐s FILE:是否存在且非空

文件是否打开:
‐fd:fd表示文件描述符是否已经打开且与某终端相关
‐N FILE:文件自动上一次读取之后是否被修改过;
‐O FILE:当前用户是否为文件的属主;
‐G FILE:当前有效用户是否为文件数组;

双目测试:
FILE1 ‐ef FILE2 :FILE1与FILE2是否指向同一个设备上的相同inode
FILE1 ‐nt FILE2:FILE1是否新于FILE2
FILE1 ‐ot FILE2:FILE1是否旧于FILE2

组合测试条件

逻辑运算符:
&&代表的意思是当前一个命令执行成功时会继续执行后续的命令,当前一个命令执行失败的时候不会执行后续的命令
||代表的意思是当前一个命令执行成功时不会继续执行后续的命令,当前一个命令执行失败的时候会执行后续的命令

第一种方式:
COMMAND1 && COMMAND2
COMMAND1 || COMMAND2

! COMMAND
第二种方式:
EXPRESSION1 ‐a EXPRESSION2
EXPRESSION1 ‐o EXPRESSION2
! EXPRESSION
Note:必须使用测试命令进行

选择执行

  • 单分支选择结构
if 判断条件;then
条件为真的分支代码
fi
  • 双分支选择结构
if 判断条件;then
条件为真的分支代码
else
条件为假的分支代码
fi
  • 多分支选择结构
if 判断条件;then
条件为真的分支代码
elif 判断条件;then
条件为真的分支代码
else
条件为假的分支代码
fi
  • 练习题

判断用户是否存在,如果不存在添加用户,并设置密码和用户相同

read -p "请输入用户名" num
id $num
if [ $? -eq 0 ];then
        echo "用户已存在"
else
        echo "用户不存在,即将为您创建用户"
        useradd $num
        echo "$num" | passwd --stdin $num
        echo "用户已创建,请登录"
fi

用户交互

read命令

  • 常用选项:
    • ‐a:将内容读入到数组
      • echo ‐n “Input muliple values into an array:” #-n表示不换行
      • read -a array
      • echo “get ${#array[@] values in array”
    • -d:表示delimiter,即定界符,例如输入为hello m,有效值为”hello”,请注意m前面的空格等会被删除
    • -e:只用于互相交互的脚本
    • -n:用于限定最多可以有多少字符可以作为有效读入
    • -p:用于给出提示符,例如:echo -n “…”来给出提示符,考科一使用read -p “…mypromt?”value的方式只需一个语句来表示
    • -r:特殊字符生效(/n)等,也应采用-r选项
    • ‐s : 对于一些特殊的符号不打印的情况
    • ‐t :用于表示等待输入的时间(s),等待时间超过,将继续执行后面的脚本
  • 练习:提示为:“input your name:”,输入姓名后,进行输出
#!/bin/bash 
read -p "please input your name:" name
echo $name
exit 0

循环语句

for循环

  • 循环体:需要执行的语句,可能执行n遍
  • 语法
for 变量名 in 列表;do
循环体
done
  • 执行机制:依次将列表中的元素赋值给“变量名”;每次赋值后执行一次循环体;直到列表中的元素耗尽,循环结束
  • 练习题1:创建用户user1‐user10家目录,并且在user1‐10家目录下创建1.txt‐10.txt
#!/bin/bash
for i in {1..10};do
mkdir /home/user$i
for j in (seq 1 10);do
touch /home/user$i/user$j.txt
done
done
  • 练习题2:批量测试地址是否在线
for i in {1..255}
do
ping -c2 -W1 192.168.184.${i} &> /dev/null
if [ $? -eq 0 ]
then
echo "192.168.184.${i}"在线""
else
echo "192.168.184.${i}"不在线""
fi
done

while循环

  • 语法
while 测试条件;do
循环体
done
  • 经典使用
#!/bin/bash
while read a;do
echo $a
done < /datas/6files
  • 练习题1:列出乘法口诀
i=1
j=1
while (($i<=9))
do
while (($j<=$i))
do
let x=$i*$j
echo -n "${i}*${j}=${x}  "
let j+=1
done
let i+=1
let j=1
echo ""
done
  • 练习题2,猜随机数游戏
x=`echo $[RANDOM%100+1]`
a=0
while true;do
let a+=1
read -p "请输入你猜的数:" y
if [ $y -eq $x ]
then
echo "你猜对啦!正确答案就是${y}"
break
elif [ $x -gt $y ]
then
echo "往大点猜了试试吧!"
else
echo "往小点猜了试试吧!"
fi
done
echo "你猜了${a}次"

until循环

  • while的是条件是测真值,until的条件式测假值
  • 语法
until 条件测试;do
循环体
done
  • 练习:列出乘法表
a=1
b=1
until ((a > 9));do
until ((b>a));do
let "c=a*b" #声明变量c
echo -n "$a*$b=$c "   #-n的意思是不换行
let b++
done
let a++
let b=1 #因为每个乘法表都是1开始乘,所以b要重置
echo "" #显示到屏幕换行
done

函数

  • 语法
function FUNNAME()
{
	函数体
	返回值
}
FUNNAME #调用函数
  • 实例1
#!/bin/bash
demoFUN()
{
	echo "这是我的第一个 shell 函数!"
}
echo "‐‐‐‐‐函数开始执行‐‐‐‐‐"
demoFUN
echo "‐‐‐‐‐函数执行完毕‐‐‐‐‐"
  • 带返回值并且调用返回值
funWithReturn()
{
	echo "这个函数会对输入的两个数字进行相加运算..."
	echo "输入第一个数字:"
	read aNum
	echo "输入第二个数字:"
	read anotherNum
	echo "两个数字分别为 $aNum$anotherNum !"
	return $(($aNum+$anotherNum))
}
funWithReturn
echo "输入的两个数字之和为 $? !"
#使用$?来获取返回值
  • 函数参数
funWithReturn()
{
	echo "第一个参数为 $1 !"
    echo "第二个参数为 $2 !"
    echo "第十个参数为 $10 !"
    echo "第十个参数为 ${10} !"
    echo "第十一个参数为 ${11} !"
    echo "参数总数有 $# 个!"
	echo "作为一个字符串输出所有参数 $* !"
}
funWithReturn 1 2 3 4 5 6 7 8 9 34 73
注意:$10 不能获取第十个参数,获取第十个参数需要${10}。当n>=10时,需要使用${n}来获取参数。

调试脚本

  • -x:在执行时显示参数和命令
  • +x:禁止调试
  • -v:当命令行进行读取时显示输入
  • +v:禁止打印输入
  • -n:检测脚本中的语法错误

环境配置

bash配置文件

  • 生效范围分类
    • 全局配置
      • /etc/bashrc
      • /etc/profile
      • /etc/profile.d/*.sh
    • 个人配置
      • ~/.bash_profile
      • ~/.bashrc
  • 功能分类
    • profile类:为交互式的shell提供配置
    • bashrc类:为非交换式的shell提供配置
  • shell登录
    • 交互式登录:su - USERNAME
    • /etc/profile –> /etc/profile.d/*.sh –> ~/.bash_profile –> ~/.bashrc –> /etc/bashrc
    • 非交换式登录:su USERNAME
  • 编辑配置文件定义的新设置的生效方式
    • 重新启动shell进程
    • 使用source命令

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

把这个文件放到/etc/profile的最后一行

#!/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)
MemoryTotal=$(free -h | awk '{print $1"    "$2}')
DiskCapacity=$(df -hl | awk '{print $1"    "$2}')
FreeDiskCapacity=$(df -h | awk '{print $1"    "$4}')
CPUauditing=$(cat /proc/cpuinfo | grep 'process' | sort | uniq | wc -l)
echo "当前系统版本是:$System"
echo "当前系统内核是:$Kernel"
echo "当前虚拟平台是:$Virtualization"
echo "当前主机名是:$Statichostname"
echo "当前网卡$wangka的地址是:$Ens32"
echo "当前lo0接口的地址是:$Lo"
echo "当前公网地址是:$NetworkIp"
echo "内存总量为:"
echo "$MemoryTotal"
echo "当前内存剩余量:"
echo "`free -h | awk -F" "+ 'NR==2{print "Men还剩"$4}NR==3{print "Swap还剩"$4}'`"
echo "磁盘总容量为:"
echo "$DiskCapacity"
echo "磁盘剩余量为:"
echo "$FreeDiskCapacity"
echo "CPU核数为:$CPUauditing"

案例,监控httpd进程

#需求:
#1.每隔10s监控httpd的进程数,若进程数大于等于500,则自动重启Apache服务,并检测服务是否重启成功
#2.若未成功则需要再次启动,若重启5次依旧没有成功,则向管理员发送告警邮件(使用echo输出已发送即可),并退出检测
#3.如果启动成功,则等待1分钟后再次检测httpd进程数,若进程数正常,则恢复正常检测(10s一次),否则放弃重启并向管理员发送告警邮件,并退出检测

#!/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 10
   	return 0
fi
}
# 每十秒钟执行一次函数,检查进程是否正常
while true;do
	check_httpd_process_number
	[ $? -eq 1 ] && exit
done

案例,监控MySQL进程

需求 与监控http进程的相同

function check_mysql_process_number()
{
num=`mysql -uroot -p123456 -e"show processlist;" 2>/dev/null | wc -l`
if [ $num -gt 10 ];then
        systemctl restart mysqld &> /dev/null
        systemctl status mysqld &> /dev/null
        if [ $? -ne 0 ];then
                num_restart_mysql=0
                while true;do
                        let num_restart_mysql++
                        systemctl restart mysqld &> /dev/null
                        systemctl status mysqld &> /dev/null
                        [ $? -eq 0 ] && break
                        [ $num_restart_mysql -eq 6 ] && break
                done
        fi
        systemctl status mysqld &> /dev/null
        [ $? -ne 0 ] && echo "mysql未正常重启,请立即查看!!" && return 1
        sleep 60
        num=`mysql -uroot -p123456 -e"show processlist;" 2>/dev/null | wc -l`
        if [ $num -gt 10 ];then
                echo "MySQL经过重启,进程数仍然大于10"
                return 1
        else
                echo "进程数小于50"
                sleep 10
                return 0
        fi
fi
}

while true
do
check_mysql_process_number
[ $? -eq 1 ] && exit
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

: ‘{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 == KaTeX parse error: Undefined control sequence: \t at position 38: …echo -e "两边共同文件\̲t̲\t\tserver1_filename"
temp_flag=1 #找到了相同的文件
break
fi
done
if [ KaTeX parse error: Undefined control sequence: \t at position 46: …-e "server1不同文件\̲t̲\t\ti"
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 [ KaTeX parse error: Undefined control sequence: \t at position 46: …-e "server2不同文件\̲t̲\t\ti"
fi
done


  • 31
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值