跳至主要內容

shell脚本语言

知识库编程技巧Shell脚本Shell脚本大约 24 分钟

shell脚本入门

| shell是什么

  • Shell是一个命令解释器,它在操作系统的最外层,负责直接与用户对话,把用户的输入解释给操作系统,并处理各种各样的操作系统的输出结果,输出屏幕返回给用户

  • 这种对话方式可以是:

    1. 交互的方式:从键盘输入命令,通过/bin/bash的解释器,可以立即得到shell的回应
    1. 非交互的方式:脚本

| Shell能做什么

  1. 安装操作系统:CentOS6.XCentOS7.X手动方式安装或克隆方式自动化安装:cobblerkickstart 底层都是shell脚本实现

  2. 优化 SSH:关闭Selinux 优化防火墙,放行 80、443、SSH端口、zabbix监控等服务访问端口

    个人需求:加大文件描述符、时间同步、硬件时间、软件时间、YUM源等,都可以写入shell脚本

  3. 安装服务 Nginx、Apache Tomcat、PHP、MySQL、Redis、Mongo、docker等例如:PHP5.4和PHP7.1写入shell脚本,实现自动化安装不同版本的服务

  4. 代码上线:shell脚本自动化发布自动化回滚

  5. zabbix监控:硬件、软件、进程、端口号、自定义监控都可以通过shell脚本+定时任务完成

  6. 日志分析日志统计:命令三剑客+定时任务+shell脚本来实现在ELK集群上的日志展示

  7. 业务层面

  8. 辅助开发程序:nohup和python的大量的日志处理

| 如何Shell编程

  1. 重点掌握前面的内容:变量、判断、bash,对它们彻底理解
  2. 先看懂,读懂shell脚本
  3. 讲完判断,将前面学过的脚本进行完善
  4. 自己写简单的脚本,如一些小的项目:生活中:随机点餐、大保健、会员办理,消费、服务 对应价格不同 结账 会员账号 密码 密码丢失
  5. 有基本适合自己的教材,如:跟老男孩学习Shell编程或者完善的文档
  6. 不能拿来及用,要搞懂、变成自己的,吸收了后可以解决企业中大部分的shell问题

一个shell脚本

# cat test.sh
#!/bin/bashecho "Hello World!"

| 执行脚本的三种常用的方式

(1)使用bash或者sh通过解释器执行脚本

在子shell中执行命令

sh test.sh
## Hello World!
bash test.sh
## Hello World!

(2)使用路径方式

全路径执行方式或者当前路径,必须给x权限

chmod +x test.sh
/server/scripts/test.sh
## Hello World!
./test.sh
## Hello World!

(3)使用source或者 . 的方式

. test.sh
## Hello World!
source test.sh
## Hello World!

(4)其他shell的执行方式

cat test.sh | bash
## Hello World!
echo ls | bash 
## test.sh
bash < test.sh
## Hello World!

shell变量基础

| 什么是变量

shell变量是一种很**“弱”**的变量,默认情况下,一个变量保存一个串,shell不关心这个串是什么含义,所以若要进行数学运算,必须使用一些命令例如let、declare、expr、双括号等

shell变量可分为两类:局部变量和环境变量

  • 局部变量只在创建它们的shell中可用
  • 环境变量则可以在创建它们的shell及其派生出来的任意子进程中使用

有些变量是用户创建的,其他的则是专用shell变量

  • 变量名必须以字母或下划线字符开头,其余的字符可以是字母、数字(0~9)或下划线字符
  • 变量名字是大小写敏感的,环境变量推荐设置为字母大写
  • 给变量赋值时,等号周围不能有任何空白符,为了给变量赋空值,可以在等号后跟一个换行符

**变量删除:**用set命令可以查看所有的变量,unset var命令可以清除变量var,var相当于没有定义过

**变量只读:**readonly var可以把var变为只读变量,定义之后不能对var进行任何更改

对shell变量的引用方式很多,用这些方式可以方便的获取shell变量的值,变量值的长度,变量的一个字串,变量被部分替换后的值等等

| 变量值的定义

字符串定义

name=Iamlizhenya
name="I am lizhenya"
echo $name
## I am lizhenya

**双引号和单引号的区别:**双引号解析变量,单引号所见即所得不能解析变量,不加引号可以解析变量

数字的定义

age="12 23 432"
echo $age
## 12 23 432

命令的定义

通过``调用命令
date +%F-%H-%M-%S
## 2022-07-07-18-02-34
time=`date +%F-%H-%M-%S`
echo $time
## 2022-07-07-18-02-40
# 时间是固定 每次调用都相同
echo $time
## 2022-07-07-18-02-40
通过$()调用命令
time=$(date +%F-%H-%M-%S)
echo $time
## 2022-07-07-18-02-52

| 变量可以定义变量

ip=`ifconfig eth0|awk 'NR==2{print $2}'`
echo $ip
## 10.0.0.7
dir=${ip}_${time}
echo $dir
## 10.0.0.7_2022-07-07-18-11-34

当shell脚本中出现2条以上相同的命令就将它们写成变量

| 核心位置变量

名称含义
$#传给脚本的参数个数
$0脚本本身的名字
$1传递给该shell脚本的第一个参数
$2传递给该shell脚本的第二个参数
$@传给脚本的所有参数的列表
$*以一个单字符串显示所有向脚本传递的参数,与位置变量不同,参数可超过9个
$$脚本运行的当前进程ID号
$?显示最后命令的退出状态,0表示没有错误,其他表示有错误
  • $0 的使用
cat test.sh
## #!/bin/bashecho $0

sh test.sh
## test.sh
sh /server/scripts/test.sh
## /server/scripts/test.sh

## $0 的使用方法
## 在脚本给予用户提示 如何使用脚本
cat test.sh 
## #!/bin/bash
## echo $0
## echo $"Usage: $0 {start|stop|status|restart|force-reload}"
sh test.sh
## test.sh
## Usage: test.sh {start|stop|status|restart|force-reload}
  • $n 脚本的参数
cat test.sh
## #!/bin/bash
## echo $1

sh test.sh 
## oldboyoldboy

## 序列传参
cat test.sh
## #!/bin/bash
## echo $3
sh test.sh {a..z}
## c
sh test.sh {1..10}
## 3
  • $# 获取脚本传参的总个数
cat test.sh
## #!/bin/bash
## echo $1 $2 $3
## echo $#

sh test.sh 1 2 3
## 1 2 3
## 3
sh test.sh {1..20}
## 1 2 3 
## 20
  • $? 获取上一条命令的返回值
ls
echo $?
## 0

llll
echo $?
## 127

## 案例:
cat ping.sh 
## #!/bin/bash
## ping -c1 -W1 $1 &>/dev/null
## [ $? -eq 0 ] && echo "$1 通的" || echo "$1 不通"
sh ping.sh www.baidu.com
## www.baidu.com 通的
sh ping.sh www.baidu.commmmmm
## www.baidu.commmmmm 不通
  • $$ 在有多个相同名称的shell环境中使用

| 脚本传参的三种方式

    1. 直接传参
cat test.sh
## #!/bin/bash
## echo $1 $2
sh test.sh oldboy 100
## oldboy 100
    1. 赋值传参
cat test.sh
## #!/bin/bash
## name=$1
## age=$2
## echo $name
## echo $age
sh test.sh oldboy 200
## oldboy
## 200

cat test.sh
## #!/bin/bash
## name=$1
## age=$2
## echo 姓名: $name
## echo 年龄: $age
sh test.sh oldboy 100
## 姓名: oldboy
## 年龄: 100
    1. read读入
read -p "请输入你的姓名: " name请输入你的姓名: oldboy
echo $name
## oldboy

## 第一种书写方式
cat test.sh
## #!/bin/bash
## read -p "请输入你的姓名: " name 
## read -p "请输入你的年龄: " agee
## cho name=$name
## echo age=$age

## 第二种书写方式
cat test.sh
## #!/bin/bash
## read -p "请输入你的姓名和年龄: " name age 
## echo name=$name
## echo age=$age

shell变量子串

| 子串的切片

name=1234567
echo ${name:2:2}
## 34
echo ${name:2:3}
## 345
echo ${name:2:4}
## 3456

| 子串的长度统计

name=1234567
## 第一种统计方式:
echo $name|wc -L
## 第二种统计方式:
expr length "$name"
## 第三种统计方式:
echo $name|awk '{print length}'
## 第四种统计方式:
echo ${#name}

| 子串的删除(支持通配符)

# url='www.baidu.com'

## 1.从前面往后删除 (贪婪匹配)
echo ${url#www.}
## baidu.com
echo ${url#*.}
## baidu.com
echo ${url#*.*c}
## om
echo ${url#*.*.}
## com
echo ${url##*.}
## com
## 2.%从后面往前删除
echo ${url%.*}
## www.baidu
echo ${url%.*.*}
## www
echo ${url%%.*}
## www

| 子串的替换

## 格式:/替换谁/替换成谁  /// 贪婪替换
url='www.baidu.com'

echo ${url/w/W}
## Www.baidu.com
echo ${url//w/W}
## WWW.baidu.com
echo ${url/www/WWW}
## WWW.baidu.com
echo ${url/baidu/sina}
## www.sina.com

shell数值运算

| expr 只支持整数运算

expr 1 + 1
## 2
expr 10 - 10
## 0
expr 10 \* 10
## 100
expr 10 / 10
## 1

## 错误运算方式:
expr 1 + 1.5
## expr: non-integer argument
expr 10 * 10
## expr: syntax error

## 案例: 判断传入的参数是否为整数
cat expr.sh
## #!/bin/bash
## read -p "请输入你的年龄: " age
## expr 1 + $age &>/dev/null
## [ $? -ne 0 ] && echo "请输入整数" && exite
## cho $age

| $(()) 只支持整数运算

注意不要和$()冲突了 执行的命令和``相同

echo $((10+10))
## 20
echo $((10-10))
## 0
echo $((10*10))
## 100
echo $((10/10))
## 1

## 随机数取余数 RANDOM 0-32767之间的数字
echo $((RANDOM%100+1))
## 82

| $[] 只支持整数运算

echo $[10+10]
## 20
echo $[10-10]
## 0
echo $[10/10]
## 1
echo $[10*10]
## 100

## 平方:
echo $[10**10]
## 10000000000

| let 只支持整数运算

let a=1+1
echo $a
## 2

let a=1*10
echo $a
## 10

i++
let i++  =========== let i=i+1
let i=i+1
echo $i
## 2
let i++
echo $i
## 3

## 直接运算 ++i i++ 相同 都是自增1++a
let ++a
echo $a
## 1

在使用变量的情况下两个是有区别i++先赋值在运算 | ++i先运算在赋值

a=1
b=1
let i=a++
let c=++b
echo $i
## 1
echo $c
## 2

| bc 支持整数和小数运算

echo 10+10|bc
## 20
echo 10+10.5|bc
## 20.5
echo 10*10.5|bc
## 105.0
echo 10-10.5|bc
## -.5
echo 10/10.5|bc
## 0

| awk 支持整数和小数运算

awk 'BEGIN{print 10+10}'
## 20
echo 10 20|awk '{print $1+$2}'
## 30
echo 10 20|awk '{print $1*$2}'
## 200
echo 10 20|awk '{print $1/$2}'
## 0.5
echo 10 20|awk '{print $1^$2}'
## 100000000000000000000

条件表达式

| 文件表达式

语法结构:

第一种:

test -f /etc/hosts && echo "文件存在"
## 文件存在
test -f /etc/host && echo "文件存在" || echo "文件不存在"
## 文件不存在
test -f /etc/hosts && echo "文件存在" || echo "文件不存在"
## 文件存在

第二种: 常用

[ -f /etc/passwd ] && echo "文件存在" || echo "文件不存在"
## 文件存在
[ -f /etc/passwdddd ] && echo "文件存在" || echo "文件不存在"
## 文件不存在
[ -d /etc/passwd ] && echo "文件夹存在" || echo "文件夹不存在"
## 文件夹不存在
[ -d /etc/ ] && echo "文件夹存在" || echo "文件夹不存在"
## 文件夹存在
[ -x /etc/ ] && echo "可执行文件存在" || echo "可执行文件不存在"
## 可执行文件存在
ll -d /etc
## drwxr-xr-x. 79 root root 8192 Oct 19 09:14 /etc
[ -e /etc/ ] && echo "文件存在" || echo "文件不存在"
## 文件存在
[ -w /etc/hosts ] && echo "可写文件存在" || echo "可写文件不存在"
## 可写文件存在

注意: 表达式中支持变量和命令


dir=/tmp
[ -d $dir ] && echo "文件存在" || echo "文件不存在"
## 文件存在
dir=/tmppppp
[ -d $dir ] && echo "文件存在" || echo "文件不存在"
## 文件不存在

ls -d /etc/sysconfig/
## /etc/sysconfig/
[ -d `ls -d /etc/sysconfig/` ] && echo "文件存在" || echo "文件不存在"
## 文件存在

| shell数值比较

语法结构第一种:test 整数1 比较符 整数2第二种:[ 整数1 比较符 整数2 ]

比较符作用
-eq等于
-ge大于或者等于
-gt大于
-le小于或者等于
-lt小于
-ne不等于
test 10 -eq 10 && echo 成立 || echo 不成立
## 成立
test 10 -ne 10 && echo 成立 || echo 不成立
## 不成立
[ 10 -eq 10 ] && echo 成立 || echo 不成立
## 成立
[ 10 -ne 10 ] && echo 成立 || echo 不成立
## 不成立
[ 15 -ne 10 ] && echo 成立 || echo 不成立
## 成立
[ 15 -gt 10 ] && echo 成立 || echo 不成立
## 成立
[ 10 -ge 10 ] && echo 成立 || echo 不成立
## 成立
[ 10 -le 10 ] && echo 成立 || echo 不成立
## 成立

## 支持命令
[ 50 -gt `echo $((RANDOM%100))|tee file.txt` ] && echo "成立" || echo 不成立
## 成立
cat file.txt 
## 20

流程控制语句

| if判断语法格式

单分支: 一个条件一个结果

if [ 条件表达式 ];then
  命令的集合
fi
if [ 条件表达式 ]
then
  命令的集合
fi

双分支结构: 一个条件 两个结果

if [ 条件表达式 ]
then
  执行的命令
else
  否则执行什么命令
fi

多分支: 多个条件 多个结果

if [ 条件表达式 ];then
  成立执行的命令
elif [ 条件表达式 ];then
  成立执行的命令
elif [ 条件表达式 ];then
 成立执行的命令
else
  以上条件都没匹配到 执行的命令
fi

案例1:根据不同的操作系统版本 安装不同的YUM源

  1. 如何查看操作系统的版本cat /etc/redhat-release
  2. 使用if多分支进行判断
  3. 执行不同的命令
  4. 测试
#!/bin/sh

[ -f /etc/init.d/functions ] && . /etc/init.d/functions
## 更新前进行备份
backup_yum='mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup'
os_version=`cat /etc/redhat-release |awk '{print $(NF-1)}'`

## 判断网络是否正常
ping -c1 -W1 developer.aliyun.com &>/dev/null
if [ $? -ne 0 ];then
  echo "网络不正常正在重启网卡请稍等...."
  systemctl restart network
    ping -c1 -W1 developer.aliyun.com &>/dev/null
    [ $? -ne 0 ] && echo "请管理员检查网络 sendmail....."
fi

## 判断wget是否安装
which wget &>/dev/null
if [ $? -ne 0 ];then
  echo "正在安装wget 请稍等....."
  yum -y install wget &>/dev/null
  [ $? -eq 0 ] && echo "wget 安装成功将继续更新YUM源....."
fi

## 根据不同的操作系统版本安装不同的YUM源
if [ ${os_version%%.*} -eq 7 ];then
 $backup_yum
 echo "正在更新YUM仓库请稍后......."
 wget -O /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-7.repo &>/dev/null
 if [ $? -eq 0 ];then
    action "成功更新阿里云YUM仓库"   /bin/true
 else
    action "更新失败请检查网络" /bin/false
 fi
elif [ ${os_version%%.*} -eq 6 ];then
 $backup_yum
 wget -O /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-6.repo
elif [ ${os_version%%.*} -eq 8 ];then
 $backup_yum
 wget -O /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-8.repo
fi

案例2:使用if判断比较两个的数字大小

#!/bin/sh

# 判断num1数字如果是不是纯数字 或者num2不是纯数字 表达式都成立
read -p "请输入两个数字: " num1 num2
if [[ ! $num1 =~ ^[0-9]+$ || ! $num2 =~ ^[0-9]+$ ]];then
  echo "请输入整数"
  exit
fi

# 比较两数大小
if [ $num1 -gt $num2 ];then
   echo "$num1>$num2"
elif [ $num1 -lt $num2 ];then
   echo "$num1<$num2"
else
   echo "$num1=$num2"
fi

案例3:安装不同的PHP版本

#!/bin/sh

cat<<EOF
1.PHP5.4
2.PHP5.5
3.PHP7.1
4.PHP7.3
EOF

read -p "请选择要安装的版本的编号或者是PHP版本号:[1|PHP5.4] " num
if [ $num = 1 -o $num = "PHP5.4" ];then
  echo "yum -y install PHP5.4........"
elif [ $num = 2 -o $num = "PHP5.5" ];then
  echo "yum -y install PHP5.5........"
elif [ $num = 4 -o $num = "PHP7.3" ];then
  echo "yum -y install PHP7.3........"
fi

| for循环

语法格式:

for 变量 in 值的列表   值: 数字 字符串 命令 序列 默认以空格来分隔
do
  执行的动作 命令的集合
done

输出结果可以和变量相关 也可以不相关

## 和变量相关:
# cat for.sh
#!/bin/sh
for num in 1 2 3
do
   echo $num
done

## 和变量不相关:
# cat for.sh
#!/bin/sh
for num in 1 2 3
do
   echo hehe
done

## 统计for循环总共循环了多少次:
# cat for.sh
#!/bin/sh
for num in {1..1000}
do
  let i++
done
echo $i

for循环 值为命令

输出结果可以和变量相关 也可以不相关

# cat for.sh
#!/bin/sh
for num in `cat /etc/hosts`
do  
  echo $num
done

for循环案例:

ping 一个c的地址 通表示在线 不通表示离线 10.0.0.1-10.0.0.254

# cat for.sh
#!/bin/sh
for i in {1..254}
do
  ping -c 1 10.0.0.${i} &>/dev/null
  [ $? -eq 0 ] && echo "10.0.0.${i} 服务器在线" || echo "10.0.0.${i} 服务器不在线"
done

for循环案例: 批量创建用户

# !/bin/sh
read -p "请输入要创建用户的个数: " num
read -p "请输入要创建用户的前缀: " prefix
for i in `seq $num`
do
  user=${prefix}$i
  id $user &>/dev/null
  if [ $? -ne 0 ];then
    useradd $user &>/dev/null
    [ $? -eq 0 ] && echo "$user 用户创建成功"
  else
    echo "useradd: user $user already exists"
  fi
done

for循环添加和删除用户 不带密版

# !/bin/sh
read -p "请输入用户的个数: " num
read -p "请输入用户的前缀: " prefix
echo "当前输入的用户名为:"
for i in `seq $num`
do
  echo "${prefix}$i"
done

read -p "请问您是要删除以上用户还是创建以上用户[del|add]: " re

if [ $re = add ];then
  for i in `seq $num`
  do
    user=${prefix}$i
    id $user &>/dev/null
    if [ $? -ne 0 ];then
      useradd $user &>/dev/null
      [ $? -eq 0 ] && echo "$user 用户创建成功"
    else
      echo "useradd: user $user already exists"
    fi
  done
elif [ $re = del ];then
  for i in `seq $num`
  do
    user=${prefix}$i
    id $user &>/dev/null
    if [ $? -eq 0 ];then
      userdel -r $user &>/dev/null
      [ $? -eq 0 ] && echo "$user 用户删除成功"
    else
      echo "用户不存在"
    fi
  done
fi

| while循环

语法结构:

while  [ 条件表达式 ]  条件表达式成立(为真)则执行 否则不执行
do
  命令
done

使用实例:死循环

## 方式一:
#!/bin/sh
while true
do
  echo hehe
  sleep 1
done
## 方式二:
#!/bin/sh
while [ 10 -gt 5 ]
do
  echo hehe
  sleep 1
done
## 方式三:
#!/bin/sh
while [ -f /etc/hosts ]
do
  echo hehe
  sleep 1
done

while读取文件 取值语法结构:

while read line # line变量名称 自定义
do
  执行的命令
done < file

使用实例:

## 方式一:
#!/bin/sh
while read line
do
  echo $line
done < /etc/hosts
## 方式二:
#!/bin/sh
while read line
do
  user=`echo $line|awk '{print $1}'`
  useradd $user
  pass=`echo $line|awk '{print $2}'`
  echo $pass|passwd --stdin $user
done < user.txt

流程控制语句

命令作用
exit退出整个脚本
break跳出当前的循环
continue结束剩下的语句继续从头开始
read交互

使用实例:

## 控制语句exit:
#!/bin/sh
while true
do
   echo test.......
   exit
   echo oldboy.......
done
echo hehe...........

## 控制语句break:
#!/bin/sh
while true
do
   echo test.......
  break
   echo oldboy.......
done
echo hehe...........

## 控制语句continue:
#!/bin/sh
while true
do
   echo test.......
  continue
   echo oldboy.......
done
echo hehe...........

流程控制语句案例:

exit 创建完oldboy5用户 退出当前脚本 只能创建5个用户

#!/bin/sh
for i in `seq 5`
do
     user=oldboy$i
    id $user &>/dev/null
     if [ $? -eq 0 ];then
        exit
     else
        useradd $user &>/dev/null
        [ $? -eq 0 ] && echo "$user 创建成功"
     fi
done

break 创建完oldboy5用户 跳出本层循环继续往下执行 创建5个用户

#!/bin/sh
for i in `seq 5`
do
     user=oldboy$i
    id $user &>/dev/null
     if [ $? -eq 0 ];then
        break
     else
        useradd $user &>/dev/null
        [ $? -eq 0 ] && echo "$user 创建成功"
     fi
done
echo oldboy

continue 在oldboy1到oldboy5用户已经存在情况下继续向下创建用户

#!/bin/sh
for i in `seq 10`
do
     user=oldboy$i
    id $user &>/dev/null
     if [ $? -eq 0 ];then
        continue
     else
        useradd $user &>/dev/null
        [ $? -eq 0 ] && echo "$user 创建成功"
     fi
done
echo hehe...................

break等级跳 ,break只会跳出当前层循环

#!/bin/sh
while true
do
    echo "第一层"
    while true
    do
        echo 第二层
        sleep 1
        while true
        do
            echo 第三层
            sleep 1
            break 3
            echo oldboy.......
        done
    done
done
echo hehe...........

shell函数

  1. 函数是命令的集合 完成特定功能的代码块
  2. 函数代码块 方便复用
  3. 函数类似变量 只有先定义才能执行

**区别:**变量不调用也会执行 name=oldboy 函数只有调用才会执行代码

| 函数的定义

#!/bin/sh
fun1(){
  echo "函数的第一种定义方式"
}
fun1

function fun2 {
  echo "函数的第二种定义方式"
}
fun2

案例: 菜单

#!/bin/sh
fun1(){
  echo -e "\t\t\t\t1.包子"
  echo -e "\t\t\t\t2.麻辣烫"
  echo -e "\t\t\t\t3.小米粥"
  echo -e "\t\t\t\t4.汉堡"
  echo -e "\t\t\t\t5.烧烤"
}
fun1

| 函数复用

# cat 1.sh
#!/bin/bash
. /server/scripts/day4/test.sh
fun3
# sh 1.sh
函数的第三种定义方式
# cat test.sh
#!/bin/sh
fun1(){
  echo "函数的第一种定义方式"
}
function fun2 {
  echo "函数的第二种定义方式"
}
function fun3(){
  echo "函数的第三种定义方式"
}

案例: 函数的传参

函数调用在函数名称的后面

fun1 参数1 参数2 参数3 对应函数中的 $1 $2 $3

函数判断文件是否存在

# cat fun.sh
#!/bin/sh
fun1(){
  if [ -f $1 ];then
    echo "$1 文件存在"
  else
    echo "$1 文件不存在"
  fi
}
fun1 /etc/hosts
# sh fun.sh
/etc/hosts 文件存在

# cat fun.sh
#!/bin/sh
fun1(){
  if [ -f $3 ];then
    echo "$3 文件存在"
  else
    echo "$3 文件不存在"
  fi
}
fun1 /etc/hosts /etc/passwd /etc/fsttttttt
# sh fun.sh
/etc/fsttttttt 文件不存在

# cat fun.sh
#!/bin/sh
fun1(){
  if [ -f $1 ];then
    echo "$1 文件存在"
  else
    echo "$1 文件不存在"
  fi
}
fun1 $2
# sh fun.sh /etc/passwd /etc/hosts
/etc/hosts 文件存在

函数可以识别变量

#!/bin/sh
file=$1
fun1(){
  if [ -f $file ];then
    echo "$file 文件存在"
  else
    echo "$file 文件不存在"
  fi
}
fun1
# sh fun.sh /etc/hosts
/etc/hosts 文件存在

| 函数变量

可以识别全局变量 函数外的都是当前shell的全局变量

只在函数内生效的变量定义

# cat fun.sh
#!/bin/sh
fun1(){
  local num=20
  for i in `seq $num`
  do
    total=$[$count+$i]    
  done
  echo "当前运算的结果是: $total"
}

fun1
echo $num
# sh fun.sh
当前运算的结果是: 20

函数的返回值 return

# cat fun.sh
#!/bin/sh
fun1(){
  echo 100
  return 50
}
result=`fun1`
echo "当前函数的返回值是: " $?
echo "当前函数的执行结果: " $result

# sh fun.sh
当前函数的返回值是:  50
当前函数的执行结果:  100

利用返回值来判断

错误写法

## 错误写法一:
#!/bin/sh
fun1(){
  if [ -f $1 ];then
    return 50
  else
    return 100
  fi
}
fun1 $1

[ $? -eq 50 ] && echo "文件存在"
[ $? -eq 100 ] && echo "文件不存在"

## 错误写法二:

#!/bin/sh
fun1(){
  if [ -f $1 ];then
    return 50
  else
    return 100
  fi
}
fun1 $1
if [ $? -eq 50 ];then
  echo 文件存在
elif [ $? -eq 100 ];then
  echo 文件不存在
fi

解决方法:

[ $? -eq 50 ] && echo 文件存在 || echo 文件不存在
if [ $? -eq 50 ];then
  echo 文件存在
else
  echo 文件不存在
fi

## 赋值的方式
re=$?
if [ $re -eq 50 ];then
  echo 文件存在
elif [ $re -eq 100 ];then
  echo 文件不存在
fi

case语句

语法结构:

case 变量 in  变量 直接传参 赋值传参
  匹配模式1)
    执行的命令集合
  ;;
  匹配模式2)
    执行命令集合
   ;;
  匹配模式3)
    执行命令集合
  ;;
  *)
    无匹配后序列 执行命令集合
esac

案例

# cat case.sh
#!/bin/sh
case $1 in
  Shell)
    echo shell......
  ;;
  MySQL)
    echo MySQL......
  ;;
  Docker)
    echo docker......
  ;;
  *)
    echo hehe......
esac

# sh case.sh Shell
shell......
# sh case.sh MySQL
MySQL......
# sh case.sh Docker
docker......

或者的使用

# cat case.sh
#!/bin/sh
case $1 in
  Shell|1)
    echo shell......
  ;;
  MySQL|2)
    echo MySQL......
  ;;
  Docker|hehe)
    echo docker......
  ;;
  *)
    ## 匹配不到可以给用户执行提示
    echo "Usage: $0 [Shell|MySQL|Docker]"
esac

# sh case.sh 1
shell.....
# sh case.sh hehe
docker......
# sh case.sh Shell
shell......

案例: 使用case写一个菜单 显示系统的登录 负载 磁盘 内存等信息

  1. 先写菜单:f查看内存、w查看负载、d查看磁盘、l查看登录信息、m显示菜单
  2. 让用户输入查看的信息read -p 请输入查看的信息的编号:
  3. 使用case做判断 执行对应的命令
#!/bin/sh
menu(){
  cat<<EOF
    1.f查看内存
    2.w查看负载
    3.d查看磁盘
    4.l查看登录信息
    5.m显示菜单
    6.e退出
  EOF
}
menu
while true
do
  read -p "请输入你想查看的信息的编号或者字母:[1|f|2|w] " num
  case $num in
    1|f)
      clear
      free -h
    ;;
    2|w)
      clear
      uptime
    ;;
    3|d)
      clear
      df -h
    ;;
    5|m)
      clear
      menu
    ;;
    6|e)
      exit
    ;;
    *)
      echo "Usage: $0 [1|f|2|w]"
  esac
done

case案例:Nginx启动脚本

Nginx启动两种方式:1种是systemctl管理启动、1种是命令行直接启动以上两种同时只能使用一种启动

命令行的方式

/usr/sbin/nginx启动

/usr/sbin/nginx -s stop 停止

重启不支持 先停止在启动

/usr/sbin/nginx -s reload查看状态 过滤端口或者PID

#!/bin/sh
[ -f /etc/init.d/functions ] && . /etc/init.d/functions
nginx='/usr/sbin/nginx'
Te(){
  if [ $? -eq 0 ];then
    action "Nginx $1 is" /bin/true
  else
    action "Nginx $1 is" /bin/false
  fi
}
case $1 in
  start)
    $nginx
    Te $1
  ;;
  stop)
    $nginx -s stop
    Te $1
  ;;
  restart)
    $nginx -s stop
    sleep 1
    $nginx
    Te $1
  ;;
  reload)
    $nginx -s reload
    Te $1
  ;;
  status)
    Port=`netstat -tnulp|grep nginx|grep master|grep '\btcp\b'|awk '{print $4}'`
    echo "Nginx_Port: $Port"
    PID=`ps axu|grep nginx|grep master|awk '{print $2}'`
    echo "Nginx_PID: $PID"
  ;;
  *)
    echo "Usage $0 [start|stop|restart|reload|status]"
esac

case案例: jumpserver 跳板机

  1. 菜单显示我们可以登录的服务信息:web01 10.0.0.7、web02 10.0.0.8
  2. 选择登录的服务器
  3. 使用case匹配
#!/bin/sh
web01=10.0.0.7
web02=10.0.0.8
MySQL=10.0.0.51
BACKUP=10.0.0.41
NFS=10.0.0.31
menu(){
  cat<<EOF
    1.web01  10.0.0.7
    2.web02  10.0.0.8
    3.MySQL  10.0.0.51
    4.BACKUP  10.0.0.41
    5.NFS  10.0.0.31
    6.menu 显示菜单
    7.exit 退出
  EOF
}
menu
trap "" HUP INT TSTP
while true
do
  read -p "请输入你要登录服务器的编号或者主机名称:[1|web01|6显示菜单] " num
  case $num in
    1|web01)
      ssh root@$web01
    ;;
    2|web02)
      ssh root@$web02
    ;;
    3|MySQL)
      ssh root@$MySQL
    ;;
    4|BACKUP)
      ssh root@$BACKUP
    ;;
    5|NFS)
      ssh root@$NFS
    ;;
    6|menu)
      menu
    ;;
    7|exit)
      exit
    ;;
    *)
      echo "Usage $0 [1|web01|2|web02]"
  esac
done

case案例: jumpserver 跳板机扩展

  1. 运维 开发 权限不同:运维:all权限 所有服务器都可以连接 、开发:只能连接web1 web2
  2. 设置密码 只能失败三次
  3. 2级菜单 服务器信息
#!/bin/sh
web01=10.0.0.7
web02=10.0.0.8
MySQL=10.0.0.51
BACKUP=10.0.0.41
NFS=10.0.0.31
users(){
  cat<<EFO
    1.运维
    2.开发
    3.退出
  EFO
}
users
ops(){
  cat<<EOF
    1.web01  10.0.0.7
    2.web02  10.0.0.8
    3.MySQL  10.0.0.51
    4.BACKUP  10.0.0.41
    5.NFS  10.0.0.31
    6.menu 显示菜单
    7.exit 退出
  EOF
}
dev(){
  cat<<EOF
    1.web01  10.0.0.7
    2.web02  10.0.0.8
    3.menu 显示菜单
    4.exit 退出
  EOF
}
trap "echo 别瞎按小心爆炸" HUP INT TSTP
read -p "请输入你的身份: " au
if [ $au = 1 ];then
  >/tmp/yunwei.pwd
  while true
  do
    yunwei=`cat /tmp/yunwei.pwd | wc -l`
    if [ $yunwei -lt 3 ];then
      read -s -p "请输入运维的密码: " pass
      if [ $pass = woshiyunwei ];then
        echo "登录成功欢迎牛逼的运维!!!"
        break
      else
        echo "密码不正确请重新输入密码"
        echo 1 >> /tmp/yunwei.pwd
        continue
      fi
    else
      echo "密码错误次数太多,现已退出"
      exit
    fi
  done
ops
while true
do
read -p "请输入你要登录服务器的编号或者主机名称:[1|web01|6显示菜单] " num
case $num in
  1|web01)
 ssh root@$web01
 ;;
  2|web02)
 ssh root@$web02
 ;;
  3|MySQL)
 ssh root@$MySQL
 ;;
  4|BACKUP)
 ssh root@$BACKUP
 ;;
  5|NFS)
 ssh root@$NFS
 ;;
  6|ops)
 ops
 ;;
  7|exit)
         exit
  ;;
 *)
  echo "Usage $0 [1|web01|2|web02]"
esac
done
elif [ $au = 2 ];then
  >/tmp/kaifa.pwd  
  while true
  do
      kaifa=`cat /tmp/kaifa.pwd | wc -l`
      if [ $kaifa -lt 3 ];then
          read -s -p "请输入开发的密码: " pass
          if [ $pass = woshikaifa ];then
              echo "登录成功欢迎小小的开发!!!"
              break
          else
              echo "密码不正确请重新输入密码"
              echo 1 >>/tmp/kaifa.pwd
              continue
           fi
      else
          echo "密码错误次数太多,现已退出"
          exit
      fi
  done
  dev
  while true
do
read -p "请输入你要登录服务器的编号或者主机名称:[1|web01|3显示菜单] " num
case $num in
  1|web01)
 ssh root@$web01
 ;;
  2|web02)
 ssh root@$web02
 ;;
  3|dev)
 dev
 ;;
  4|exit)
         exit
  ;;
 *)
  echo "Usage $0 [1|web01|2|web02]"
esac
done
elif [ $au = 3 ];then
    exit
fi

shell变量数组

数组的分类

  1. 普通数组:只能用数字作为索引
  2. 关联数组:数字或者字符串作为索引

数组的结构

  1. 类似变量:一个名称对应一个值,一个筐子里面只装了一个水果(苹果)
  2. 数组:一个名词对应多个值,一个筐子里面装了很多盒子,每个盒子里有不同的水果
  3. 索引:盒子的名称,称为索引也称为下标也称为元素名称

数组的格式

数组名称[索引名称]=元素的值筐子[盒子1]=苹果筐子[盒子2]=梨筐子[盒子3]=黄瓜

普通数组的盒子(索引)从0开始筐子[0]=值

| 普通数组的定义方式

**第一种定义方式:**按照索引进行定义

array[1]=shell
array[2]=mysql
array[3]=docker

查看数组:按照索引进行查看

echo ${array[2]}
## mysql
echo ${array[1]}
## shell
echo ${array[3]}
## docker

查看数组中所有的值

echo ${array[*]}
## shell mysql docker
echo ${array[@]}
## shell mysql docker

查看数组中所有的索引(下标) 所有的盒子的名称

echo ${!array[@]}
## 1 2 3

查看索引的总个数

echo ${#array[@]}
## 3

查看系统的普通数组

declare -a

**第二种定义方式:**使用默认的索引来定义

array=(shell mysql docker oldboy)
echo ${array[*]}
## shell mysql docker oldboy
echo ${!array[*]}
## 0 1 2 3

**第三种定义方式:**使用自定义索引和默认索引定义

array=([5]=shell mysql [10]=docker hehe)
echo ${array[*]}
## shell mysql docker hehe
echo ${!array[*]}
## 5 6 10 11

第四种定义方式:

array=(`cat /etc/passwd|awk -F: '{print $1}'`)
echo ${array[*]}

## root bin daemon adm lp sync shutdown halt mail operator games ftp nobody systemd-network dbus polkitd tss abrt sshd postfix test1 test2 test3 test4 test5 zhangsan lisi erdan goudan gousheng baoyi oldboy5 oldboy1 oldboy2 oldboy3 oldboy4 oldboy6 oldboy7 oldboy8 oldboy9 oldboy10 nginx

echo ${!array[*]}
## 0 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

| 数组的遍历

unset array
array=(10 20 30 40)
echo ${array[*]}
## 10 20 30 40
for i in ${array[*]}; do
  echo $i;
done
## 10203040

案例

array=(10.0.0.1 10.0.0.7 10.0.0.254 www.baidu.com)
for i in ${array[*]};do
  ping -c1 -W1 $i;
done

cat array.sh
## #!/bin/sh
## ip=(10.0.0.1www.baidu.com10.0.0.710.0.0.8www.sinaaaa.com10.0.0.254www.weibo.com)
## for i in ${ip[*]}; do
##   ping -c1 -W1 $i &>/dev/null
##   if [ $? -eq 0 ];then
##     echo "$i 在线"
##   else
##     echo "$i 不在线"
##   fi
## done

使用索引的方式进行遍历

echo ${array[*]}
## 10.0.0.1 10.0.0.7
echo ${!array[*]}
## 0 1
echo ${array[0]}
## 10.0.0.1
echo ${array[1]}
## 10.0.0.7
for i in ${!array[*]}; do
  echo ${array[$i]};
done
## 10.0.0.110.0.0.7

| 关联数组

使用字符串作为索引,定义方式:

默认的定义方式是普通数组

array[index1]=shell
array[index2]=mysql
array[index3]=docker
echo ${array[*]}
## docker
declare -a |grep array
declare -a array='([0]="docker")'

配置关联数组 提前声明 declare -A 声明关联数组

declare -A array
array[index1]=shell
array[index2]=mysql
array[index3]=redis
echo ${array[*]}
## shell mysql redis

查看索引

echo ${!array[*]}

案例:统计当前的男性和女性出现的次数

cat sex.txtmmfmfx

## for i in `cat sex.txt`; do
##   let $i++;
## done
## echo $m3
## echo $f2
## echo $x1

执行过程

# !/bin/sh
declare -A sex
while read line
do
  let sex[$line]++
  line=m
  let sex[m]++
  line=x
  let sex[x]++
  line=f
  let sex[f]++
done < sex.txt

直接查看

# !/bin/sh
declare -A sex
while read line
do
  let sex[$line]++
done<sex.txt
echo m 出现了 ${sex[m]}echo f 出现了 ${sex[f]}echo x 出现了 ${sex[x]}

数组遍历

# !/bin/sh
declare -A sex
while read line
do  
  let sex[$line]++
done<sex.txt
for i in ${!sex[*]}
do
  echo $i 出现了  ${sex[$i]}done

案例: 统计nginx日志每个IP出现的次数

# !/bin/sh
declare -A ip
while read line
do
  let ip
  [`echo $line|awk '{print $1}'`]++
done</var/log/nginx/access.log
for i in ${!ip[*]}
do
  echo $i 出现了  ${ip[$i]}done