3-Linux脚本

shell介绍

shell脚本开头带着一个Sha-Bang出发(Sha-Bang指的是#!)
注意: shebang是一个文本行,其中#!位于解释器路径之前。

变量

规则

  • 命名只能使用英文字母,数字和下划线,首个字符不能以数字开头
  • 中间不能有空格,可以使用下划线 _

写shell脚本时,经常会使用到变量。在使用前需先声明变量,后使用变量。

声明变量
#!/bin/bash
your_name=zjz#使用变量
echo $your_name

shell传递参数

我们可以在执行 Shell 脚本时,向脚本传递参数,脚本内获取参数的格式为:$n。n 代表一个数字,1 为执行脚本的第一个参数,2 为执行脚本的第二个参数,以此类推……

参数介绍:
$0:表示执行的文件名
$1:表示为脚本传递第一个参数
$2:表示为脚本传递第二个参数
$n:表示为脚本传递第n个参数

特殊参数:
$? 显示命令退出创建,0为正常(常用)
$$ 显示脚本运行的当前进程号

$* 和 $@ 返回向脚本传递的参数

#!/bin/bashecho "-- \$* 演示 ---"
for i in "$*"; doecho $i
doneecho "-- \$@ 演示 ---"
for i in "$@"; doecho $i
done执行结果
# ./test.sh 1 2 3
-- $* 演示 ---
1 2 3
-- $@ 演示 ---
1
2
3

参数实例

# vim canshu.sh
#!/bin/bashecho "Shell 传递参数实例!";
echo "执行的文件名:$0";
echo "第一个参数为:$1";
echo "第二个参数为:$2";执行脚本如下
# bash canshu.sh 1 2
Shell 传递参数实例!
执行的文件名:canshu.sh
第一个参数为:1
第二个参数为:2

运算符

算数运算符

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

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

**注意: **

  • 条件表达式要放在方括号之间,并且要有空格,例如: [ $a==$b ] 是错误的,必须写成 [ $a == $b ]
  • 乘号(*)前边必须加反斜杠(\)才能实现乘法运算;

关系运算符

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

运算符 说明 举例
-eq 等于,相等返回 true。 [ $a -eq $b ] 返回 true。
-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。

布尔运算符

下表列出了常用的算术运算符,假定变量 a 为 10,变量 b 为 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。

字符串运算符

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

文件测试运算符

文件属性检测描述如下:

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

其他检查符:

  • -S: 判断某文件是否 socket。
  • -L: 检测文件是否存在并且是一个符号链接。

通配符

通配符一般用户命令行bash环境,而linux正则表达式用于grep,sed,awk场景。

*:代表匹配任意字符

# ll *.sh
-rw-r--r-- 1 root root 24934 Sep 22 15:17 install.sh
-rw-r--r-- 1 root root  3785 Jan 25 00:00 test.sh
-rw-r--r-- 1 root root  3085 Oct 12 16:05 vpstest.sh

?:代表任意1个字符(一个?代表一个字符)

# ll ????.sh
-rw-r--r-- 1 root root 3785 Jan 25 00:00 test.sh

;:连续不同命令的分隔符

# w;ls16:12:21 up 121 days, 23:24,  1 user,  load average: 0.09, 0.10, 0.13
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
root     pts/0    58.22.123.4      16:09    5.00s  0.02s  0.00s wcheck        httpd-count  mynginx       Python-3.7.2  test      vpstest.sh

' ':单引号所见即所得,不具有变量置换功能
" ":双引号具有变量置换功能,解析变量后输出
[]: 使用[0-9]来匹配 0~9 之间的单个数字

# ll file[1-3].txt
-rw-r--r-- 1 root root 0 Jan 29 16:16 file1.txt
-rw-r--r-- 1 root root 0 Jan 29 16:16 file2.txt
-rw-r--r-- 1 root root 0 Jan 29 16:16 file3.txt

{}:中间为命令区块组合或内容序列

# touch  file{1..6}.txt

转义字符

Shell 解释器还提供了特别丰富的转义字符来处理输入的特殊数据。

  • 反斜杠(\):使反斜杠后面的一个变量变为单纯的字符串。
  • 单引号(’’):转义其中所有的变量为单纯的字符串。
  • 双引号(""):保留其中的变量属性,不进行转义处理。
  • 反引号(``):把其中的命令执行后返回结果。

正则表达式

为处理大量的字符串而定义的一套规则和方法

^:行的开头,匹配以1开头的内容。

# grep '^1' /etc/hosts
127.0.0.1 VM-0-8-centos VM-0-8-centos
127.0.0.1 localhost.localdomain localhost
127.0.0.1 localhost4.localdomain4 localhost4
103.224.251.67 www.bt.cn

$:行的结尾,匹配以hosts结尾的内容

# grep 'host$' /etc/hosts
127.0.0.1 localhost.localdomain localhost
::1 localhost.localdomain localhost

^$:空行,匹配yum.conf文件中的空行

# grep -n  '^$'  /etc/yum.conf
14:
15:
25:

去除文件中的空行
# grep -v '^$' /etc/yum.conf > yum.conf.bak

.:点,代表且只能代表任意一个字符
(全部匹配)
在这里插入图片描述
\ :转义符号
例 . 就只代表点本身,让有着特殊身份意义的字符脱掉马甲,还原原型。

查找/etc/yum.conf文件中以点结尾的行
# grep '\.$' /etc/yum.conf  
# information.
# manually check the metadata once an hour (yum-updatesd will do this).

*: 重复0个或多个前面的一个字符
例 0*匹配没有0,有1个0或多个000
在这里插入图片描述
.*:匹配所有字符。来以任意多个字符开头。以任意多个字符结尾
在这里插入图片描述

三剑客

grep-搜索打印

用与搜索,并打印出来。

grep家族

  • grep:在文件中全局查找指定的正则表达式,并打印所有包含该表达式的行
  • egrep:扩展的egrep,支持更多的正则表达式、元字符。(和grep -E等价)
  • fgrep:固定grep fixed grep,字面解释所有的字符 (看到什么就是什么,和grep -F等价)。

语法:# grep [参数]
参数:
-i: 忽略大小写
-v:取反
-n:列出所有匹配行,显示行号
-c:只输出匹配行的数量

grep

例子1:同时匹配大小写
在这里插入图片描述
例子2:只显示匹配的内容
在这里插入图片描述
例子3:匹配小写字母
在这里插入图片描述
例子4:[^abc]匹配不包含小写字母的内容
#grep -n "[^a-z]" zjz.log

重复匹配

注意:egrep(grep -E)或sed -r 过滤一般特殊字符可以不转义

a\{n,m\)重复n到m次,a重复的字符。如果用egrep /sed-r可以去掉斜线。

  1. 匹配文章中包含0,有2-3次的行
    # grep "0\{3,4\}" num.txt
    在这里插入图片描述

a\{n\}重复至少n次,前一个重复的字符。

  1. 匹配文章中同时包含r两次的行
    在这里插入图片描述

a\{,m\}
在这里插入图片描述

egrep
  1. +表示重复“一个或一个以上”前面的字符(*是0或多个)!
    在这里插入图片描述
  2. ?表示重复“0个或一个”前面的字符
    在这里插入图片描述
  3. | 表示同时过滤多个字符串
    在这里插入图片描述
  4. ()分组过滤,后向引用。
    #grep -E “g(la|oo)d” zjz.log
    在这里插入图片描述

awk

sed-增删改查(取行就用sed)

Linux系统:读取一行,操作一行

a/i – 增

  1. a 追加文本到指定行后
    #sed '2a 106,dandan,CSO' person.txt意思是在第2行后添加一行
    在这里插入图片描述
  2. i 插入文本到指定行后
    #sed '2i 107,dan,CSO' person.txt 在第1,2行中间添加一行
    在这里插入图片描述
  3. 多行增加
    #sed '2a 108,zjz,CCO\n109,zj,COC' person.txt 在第2行后添加 两行 (\n为换行符)
    在这里插入图片描述
  4. ssh优化
    sed '20a Port 52113\nPermitRootLogin no\nPermitEmptyPasswords no\nUseDNS no\nGSSAPTAuthentication no' /etc/ssh/sshd_config

d-删

  1. nd 删除n行
    # sed '2d' person.txt
    在这里插入图片描述
  2. 删除文件内容但其中去除oldboy的行
[root@oldboy ~]# sed '/oldboy/d' person.txt # ——→删除包含"oldboy"的行
102,zhangyao,CTO
103,Alex,COO
104,yy,CFO
105,feixue,CIO

c-改

按行替换,用新行取代旧行

  1. 将文件中的第2行替换成106,dandan,CSO
# sed '2c 106,dandan,CSO' person.txt
101,oldboy,CEO
106,dandan,CSO
103,Alex,COO
104,yy,CFO
105,feixue,CIO
s###g – 文本替换

sed -i 's#▇#▲#g' oldboy.log

s:单独使用→将每一行中第一处匹配的字符串进行替换 ==>sed命令 
g:每一行进行全部替换 ==>sed命令s的替换标志之一,非sed命令 
-i:修改文件内容 ==>sed软件的选项sed软件替换模型(方框▇被替换成三角▲)
sed -i 's/▇/▲/g' oldboy.log 
sed -i 's#▇#▲#g' oldboy.log
  1. 替换文章中的某一个单词
[root@localhost ~]# sed 's#oldboy#zjz666#g' person.txt 
101,zjz666,CEO
102,zhangyao,CTO
103,Alex,C00
104,yy,CFO
105,feixue,CI0
  1. 指定行修改配置文件 CTO原来,CCO现在
    在这里插入图片描述

p – 查

输出指定内容,但默认会输出2次匹配的结果,因此使用n取消默认输出

测试文件内容

# cat test/num.txt -n1  120002  4230003  57300004  0001235  0007893436  1111
  1. 按行查询
[root@oldboy ~]# sed -n '2p' person.txt
102,zhangyao,CTO
  1. 按范围查询(从第1行开始隔两行输出一次)
# sed -n '1~2p' test/num.txt
12000
5730000
000789343
  1. 按字符串查询
# sed -n '/123/p' test/num.txt
000123
  1. 混合查询

if/for/while/until/case

if

注意:

  • [ ]表示条件测试。注意这里的空格很重要。要注意在’[‘后面和’]'前面都必须要有空格

语法结构

单分支结构
if [ 条件测试 ];then命令
fi双分支结构
if [ 条件测试 ];then 命令 
else命令 
fi多分支结构
if [ 条件测试 ];then执行的命令
elif [ 条件测试 ];then执行的命令
else执行的命令
fi

例子1:ping测试

#!/bin/bash
# ping test
# v1.0 by zjz 2019.10.30
ping www.baidu.com -c 3 &>/dev/null
if [ $? -eq 0 ];thenecho "ping正常,网络ok"
elseecho "network error"
fi

执行结果:
在这里插入图片描述

例子2.判断:如果vsftpd启动,输出详情

vsftpd服务器已启动:
vsftpd监听的地址是:
vsftpd监听的端口是:
vsftpd的进程PID是:

[root@jumpserver jiaoben]# vim vsftpd_status.sh
#!/bin/bash
#判断vsftpd状态
#v1.0 by zjz
ip=192.168.0.109
rpm -q vsftpd >>/dev/null
if [ $? -ne 0 ];thenecho "vsftpd 未安装"yum install vsftpd
fisystemctl restart vsftpdss -tnlp | grep "vsftpd" >>/dev/null
if [ $? -eq 0  ];thenvsftpd_address=$ipvsftpd_port=`ss -tnlp | grep "vsftpd" | awk '{print $4}' | awk -F ":" '{print $4}'`vsftpd_pid=`systemctl status vsftpd  |grep 'Main PID' | awk '{print $3}'`echo "vsftpd服务器已启动"echo "vsftpd_IP地址为$vsftpd_address"echo "vsftpd服务器端口为$vsftpd_port"echo "vsftpd服务进程PID为$vsftpd_pid"elseecho "vsftpd服务器未启动"
fi

执行结果:
在这里插入图片描述

例子3.判断用户输入的是否是数字

#!/bin/bash
#判断输入的是否是数字
#v1.1 by zjz 2019-10-30read -p "请输入字符:" numif [[ "$num" =~ ^[0-9]+$ ]];thenecho "你输入的是数字"
elseecho "你输入的不是数字"
fi

执行结果:
在这里插入图片描述

例子4:检查网络,自动化安装httpd服务

#!/bin/bash
# auto install apache
# v1.1 by zjz 2019.10.30
# v1.2 by zjz 2020.04.29
#route 命令需提前安装net-tools
#gataway=`route -n | grep UG | awk '{print $2}'` #得出他的网关
gateway=`routel | sed -n '2p' | awk -F' ' '{print $2}'`ping -c1 wwww.baidu.com &>/dev/null  #测试网络是否正常if [ $? -eq 0  ];then  #当ping通百度, 0=0时开始安装yum install -y httpd systemctl restart httpdsystemctl enable httpd#判断防火墙是否开启ps -aux | grep firewalld | grep -v 'color' &> /dev/nullif [ $? -eq 0 ];thenfirewall-cmd --permanent --add-service=httpfirewall-cmd --permanent --add-service=httpsfirewall-cmd --reloadecho "Firewalld strategy alread update"elseecho "Firewalld not running"fi#判断selinux是否开启getenforce | grep enforif [ $? -eq 0 ];thensed -i 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/configsetenforce 0elseecho "SELinux not running"fi#curl http://127.0.0.1
elif    ping -c1 $gataway &>/dev/null;then  #ping网关,若是通,可能就是dns问题echo "check DNS"
elseecho "检查IP配置是否正常"   #如果都排查不了就手动检查网络配置exit
fi

for

遵循原则:循环次数是固定的

语法结构

for 变量名(如:i)   in  [ 取值列表 ]
do循环体
doneC语言:
for (( 初值;条件;步长))
do循环体
done

例子1:通过ping检查主机是否存活

#!/bin/bash
# multi-ping host 使用sort将输出的ip排序
#v1.0 by zjz 2019-11-05>`date +%F`ip-up.txt    #初始化文件
>`date +%F`ip-down.txt
for i in {1..254}       #ping 1-254的ip
do{ip=192.168.0.$iping -c1 -W1 $ip &>/dev/null  #ping1次,每次1sif [ $? -eq 0 ];then   # $?判断是否ping通echo "$ip is up" >> `date +%F`ip-up.txt  # ping成功,写入文件elseecho "$ip is down" >> `date +%F`ip-down.txt   #ping失败,写入文件fi}&  #后台并发执行,加快执行速度
done
wait   
echo "=======HOST UP============"
sort -n -k 4 -t.  `date +%F`ip-up.txt    #将文件的ip排序输出 -n:依照数值的大小排序 -k 分隔符 -t<分隔字符>
echo "=======HOST DOWN=========="
sort -n -k 4 -t.  `date +%F`ip-down.txt
#sort -f `date +%F`ip-down.txtecho "all ping.."

执行结果:(能正常判断主机 是否存活)
在这里插入图片描述

例子2:通过文件中的ip地址为for循环的 取值列表

IP地址文件如下

# cat ip-aliving.txt
192.168.1.8 liv ing
192.168.1.1 living
192.168.1.66 living
192.168.1.4 living
192.168.1.5 living
192.168.1.10 living
192.168.1.15 living

脚本内容

# cat ping-ok.sh
#!/bin/bash
for ip in `cat ip-aliving.txt | awk '{print $1}'`#使用ip-aliving.txt作为取值条件
doping -c1 -W1 $ip &>/dev/nullif [ $? -eq 0 ];thenecho "$ip up"elseecho "$ip down"fi
done

执行结果
在这里插入图片描述

例子3:使用for循环,批量创建用户 前缀+序号,并添加密码

# cat create_user.sh
#!/bin/bash
#批量创建用户并设置密码
#v1.0 by  zjz 2019-11-03
while true
doread -p "Please enter prefix & passwod & num [ai 123 5]: " prefix pass num #理解为:username=prefix+num  prefix:前缀  pass:密码	 num:创建多少人用户printf "user information:   #将用户输入的值打印出来------------------user prefix: $prefixpassword:$passnum:$num-----------------"read -p "Are you sure ?[y/n]: " action   if [ $action = "y" ];thenbreakfi
donefor i in `seq -w $num` 
douser=$prefix$iid $user &>/dev/nullif [ $? -eq 0 ];then    #检测用户是否已存在echo "useradd: user $user already exists"elseuseradd $userecho "$pass" | passwd --stdin $user &>/dev/nullif [ $? -eq 0 ];thenecho "$user is created"fifi
done

执行结果
在这里插入图片描述

例子4:使用for批量远程修改主机ssh配置

# vim /etc/ssh/sshd_config
UseDNS yes  改为 UserDNS no   //不使用DNS解析,解决登录卡的问题
GSSAPIAuthentication yes 改为 GSSAPIAuthentication no
# cat change_sshconfig.sh
#!/bin/bash
#change ssh config file
#v1.0 by zjz 2019-11-04for ip in `cat ip-aliving.txt | awk '{print $1}'`  #找到需要修改的IP 
do{ping -c1 -W1 $ip &>/dev/nullif [ $? -eq 0 ];thenssh $ip "sed -ri 's/^#UseDNS/UseDNS no/g' /etc/ssh/sshd_config"              #ssh配置ssh $ip "sed -ri 's/^GSSAPIAuthentication/GSSAPIAuthentication no/g' /etc/ssh/sshd_config"   #ssh配置ssh $ip "sed -ri '/^SELINUX=enforcing/cSELINUX=disabled' /etc/selinux/config"          #修改selinux配置文件,改为disabledssh $ip "systemctl stop firewalld"                                           #关闭防火墙ssh $ip "setenforce 0"                                                       #关闭selinuxfi}&    #后台运行,加速运行
done
wait
echo "config ok..."

执行结果
在这里插入图片描述

例子5:for 计算从1加到100

#!/bin/bash
# sum 1++100=??
#v1.0 by zjz 2019-11-05for i in {1..100}  #i=1一直循环到100结束
dolet sum=$sum+$i   #使用这两种写法都是可以的#let sum+=$i      #let 为运算
done
echo "sum:$sum"

执行结果:
在这里插入图片描述

例子6:打印99乘法表

[root@web1 script]# vim 9x9v2.sh
#! /bin/bash
#打印九九乘法表
echo "=========九九乘法表========"
for i in `seq 1 9`
dofor j in `seq 1 $i`doecho -n -e "${i}x${j}=$[$i*$j]\t"doneecho
done

执行结果:
在这里插入图片描述

while

如果想让一个文件逐行处理,应提前想到while
while 当条件测试成立(为真时)执行循环体

注意: while识别 空行、空格,for 则是直接无视 空格

语法结构

while 条件(条件为真就往下执行while)
do循环体(要多次执行的部分)
done
例子1:逐行读取文件内容
# bash while.sh /file/ip.txt
while read line
do 
done < $1     #OR done < /file/ip.txt

在这里插入图片描述

例子2:使用while批量通过文件内容创建用户、密码

在这里插入图片描述

[root@localhost sh]# cat while-create-user.sh
#!/bin/bash
# while create user
#v1.0 by zjz 2019-11-04while read line
douser=`echo $line | awk '{print $1}'`pass=`echo $line | awk '{print $2}'`id $user &>/dev/nullif [ $? -eq 0 ];thenecho "user '$user' already exists"elseuseradd $userecho "$pass" |passwd --stdin $userif [ $? -eq 0 ];thenecho "$user is create"fifi
done < $1  #读入命令行后的一个参数,导入while循环

例子3:V2 解决文件里有 空行、空格

[root@localhost sh]# cat while-create-user.sh
#!/bin/bash
# while create user
#v1.0 by zjz 2019-11-04while read line
doif [ ${#line} -eq 0  ];then   # ${#line} 用于判断当前行长度是不是0#exit    #直接退出程序#break   #退出当前while循环continue #退出当前行,继续往下执行下一行fiuser=`echo $line | awk '{print $1}'`pass=`echo $line | awk '{print $2}'`id $user &>/dev/nullif [ $? -eq 0 ];thenecho "user $user already exists"elseuseradd $userecho "$pass" |passwd --stdin $userif [ $? -eq 0 ];thenecho "$user is create"fifi
done < $1

执行结果:
在这里插入图片描述

例子4:测试主机的连通性,条件为真,能ping通主机,执行while循环体(下线)

[root@localhost sh]# cat while-conn-test.sh
#!/bin/bash
#while ping host
#v1.0 by zjz 2019-11-05ip=192.168.11.137
while ping -c1 -W1 $ip &>/dev/null  #主机能ping通,条件为真,就进入while循环
dosleep 1   #停个1sbreak
done
echo "$ip is up.."  

例子5:while 计算从1加到100

[root@localhost sh]# cat while_sum1-100.sh
#!/bin/bash
# use while sum 1++100
#v1.0 by zjz 2019-11-05i=1   #赋初值i=1
while [ $i -le 100 ] #当i小于100时,条件为真,执行循环
dolet sum=$sum+$i  #定义一个sum=sum+i let i++         #运行循环后i+1,确保继续进入循环
done
echo "while sum:$sum"  

执行结果:
在这里插入图片描述

until

语法结构:

until 条件测试(条件为假就往下执行until)
do 循环体
done

例子1:测试主机的连通性,条件为假,不能ping通主机,执行until循环体(上线)

[root@localhost sh]# cat untile-conn-test.sh
#!/bin/bash
# until ping-host
#v1.0 by zjz
ip=192.168.11.136
until ping -c1 -W1 $ip &>/dev/null
dosleep 1break
done
echo "$ip is down"

例子2:until 计算从1加到100

[root@localhost sh]# cat until_sum1-100.sh
#!/bin/bash
# use while sum 1++100
#v1.0 by zjz 2019-11-05i=1   #赋初值i=1
until [ $i -gt 100 ] #i大于100,条件为假,执行循环
dolet sum=$sum+$i  #let运算,sum=sum+ilet i++         #循环后,i+1
done
echo "while sum:$sum"

执行结果:
在这里插入图片描述

case

遵循原则:匹配上就停止

语法结构

case 变量  in
模式1)命令1
;;模式2)命令2
;;*)前面都不匹配,匹配这一条
;;
esac

例子1:根据系统版本,更换aliyun-yum源

#!/bin/bash
#---------------------
#不同系统更换yum源
#v1.0  by zjz
#---------------------
#判断系统版本
os_version=`cat /etc/redhat-release |awk '{print $4}' | awk -F "." '{print $1}'`[ -e /etc/yum.repos.d/bak ] || mkdir /etc/yum.repos.d/bak
mv /etc/yum.repos.d/*.repo /etc/yum.repos.d/bak &>/dev/nullcase "$os_version" in
7)curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repocurl -o /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repoif [ $? -ne 0 ];thenecho "Aliyun-YUM仓库安装失败,请检测网络连接、DNS是否正常"exitfi;;6)curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-6.repocurl -o/etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-6.repoif [ $? -ne 0 ];thenecho "Aliyun-YUM仓库安装失败,请检测网络连接、DNS是否正常"exitfi;;esac

例子2:请用户确认要删除的用户,case判断

# cat del-user.sh
#!/bin/bash
#----------------
#del user
#v1.0 by zjz 2019-10-29
#---------------
read -p "请输入要删除的用户: " duser
id $duser  &>/dev/nullif [ -z $duser ];thenread -p "请输入要删除的用户: " duser
fiif [ $? -ne 0 ];thenecho "您输入的用户$duser不存在"exit 1   #用户不存在返回值设置为1
firead -p "你确定要删除此用户$duser吗?[y/n]:" action
case $action in
y|yes|Y|YES)       #case条件可以写或y或yes或Y或YESuserdel -r $duserecho "$duser 已删除"
;;
n|no|No|NO)echo "那就不删除了"
;;
esac

运行结果:

# bash del-user.sh
请输入
要删除的用户: zjz
你确定要删除此用户zjz吗?[y/n]:y
userdel: user 'zjz' does not exist
zjz 已删除

例子3:case 实现简单系统工具箱

# vim system_manage01.sh
#!/bin/bash
# system manage
# v1.0 by zjz 2019-10-30menu() {         #这段函数作用是 实现重复调用打印菜单,使代码变得整洁cat <<-EOF#-------系统管理工具箱----------#       h.帮助                 ##       f.磁盘分区             ##       d.文件系统挂载情况     ##       m.内存使用情况         ##       u.系统负载             ##       q.离开                 ##-------------------------------EOF
}
menu    #此menu,当用户执行脚本时,先打印上方的菜单while true   #进入死循环
doread -p "请输入[h for help]: " actioncase "$action" in   #判定用户输入的actionh) clear; menu;;f) fdisk -l;;d) df    -Th ;;m) free -m;;u) uptime;;q) break;;  #q跳出循环,而不使用exit退出脚本,原因:跳出循环后还有语句要执行"") ;;*) echo "没有此选项" ;;esac
done
echo "I am Alive"  #配合q的break,跳出循环后执行这句

执行结果:
在这里插入图片描述

数组

一.0 1 2 在Linux中代表的含义

多用于对变量切片

1.变量

使用数组进行切割

定义一个变量name=zxy,取得中间的x,就需要使用数组echo "${name:0}"

2.普通数组

只能使用整数0 1 2,作为数组索引

echo "${book[0]}"等于数组中的第一个数nginx

3.关联数组

可以使用字符串,作为数组索引

  • 定义关联数组,申明是关联数组
  • 关联数组declare -A [数组名],
  • 定义关联数组tt=([index1]=test [index2]=test2)。index1为索引值,test为内容
  • 查看数组内容
#echo ${tt[@]}
test test2

若未提前关联数组,此时查看数组内容则只输出test2

#echo "${tt[@]}"
test2
  1. 查看索引下标
#echo ${!tt[@]}
index1 index2

// 每个索引进行单个赋值,流程如下:

# array1[0]=pear
# array1[1]=apple
# array1[2]=orange
# array1[3]=peach
# echo ${array1[@]}pear apple orange peach
# echo ${array1[1]}apple

// 一次赋值多个值,流程如下:

索引=下标

[root@localhost ~]# arrary2=(tom jack alice)
[root@localhost ~]# echo ${!arrary2[@]}
0 1 2
[root@localhost ~]# echo "${arrary2[0]}"
tom分号""赋值:
[root@localhost ~]# arrary3=(tom jack alice "mike jordan")  
[root@localhost ~]# echo "${arrary3[@]}"
tom jack alice mike jordan
[root@localhost ~]# echo "${!arrary3[@]}"
0 1 2 3#分号内为一个赋值,“mike jordan”是一个整体
[root@localhost ~]# echo "${arrary3[3]}"
mike jordan定义索引(下标)名称
[root@localhost ~]# array4=(1 2 3 "a and b" [20]=world)
[root@localhost ~]# echo "${array4[@]}"
1 2 3 a and b world
查看数组的索引名,会出现个20,而不是4。因为20为自定义,直接跳到20
[root@localhost ~]# echo "${!array4[@]}"
0 1 2 3 20
[root@localhost ~]# echo "${array4[3]}"
a and b
查看自定义索引名的内容,[20]=world
[root@localhost ~]# echo "${array4[20]}"
world

//将/etc/passwd文件中的每一行作为元数赋值给数组 array5

[root@localhost ~]# cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
......省略
[root@localhost ~]# array5=(`cat /etc/passwd`)
[root@localhost ~]# echo "${array5[0]}"
root:x:0:0:root:/root:/bin/bash
[root@localhost ~]# echo "${array5[1]}"
bin:x:1:1:bin:/bin:/sbin/nologin

//循环数组的 索引key,并循环数组的 内容value

i++ 例如int i=1,a=0; a=i++; —->a=i;—->a=a+1;—–>a=1

[root@nothingzh shell]# bash hosts_array.sh
#!/bin/bash#declare -A hosts_array
#1.对数组进行赋值,定义索引,$line赋值到内容
while read line
dohosts_array[++i]=$line  #++i用于先运算等1,循环后+1# $line用于读入/etc/hosts的每一行内容
done </etc/hosts
echo "first的索引值是: ${hosts_array[1]}"
#2.对数组进行遍历循环,分别输出索引对应的内容
#	${!hosts_array[*]}=1 2 3 因为/etc/hosts文件中只有3行
for j in ${!hosts_array[*]}  #${!hosts_array[*]}获取数组的索引,下方echo配合j的变量输出1 2 3 索引下的内容
doecho "索引是:$j,hosts的内容:${hosts_array[$j]}"
done

4.查看数组赋值结果

可理解为[key]=“value”

[root@localhost ~]# declare -a
declare -a BASH_ARGC='()'
declare -a arrary2='([0]="tom" [1]="jack" [2]="alice")'
declare -a arrary3='([0]="tom" [1]="jack" [2]="alice" [3]="mike jordan")'
declare -a array4='([0]="1" [1]="2" [2]="3" [3]="a and b" [20]="world")'
declare -a array5='([0]="root:x:0:0:root:/root:/bin/bash" [1]="bin:x:1:1:bin:/bin:/sbin/nologin" [2]="daemon:x:2:2:daemon:/sbin:/sbin/nologin" 
....

5.数组的常见用法

#号为统计个数符号
*,@ 为访问所有元素
! 为获取数组的索引

统计数组个数
[root@localhost ~]# array3=(tom jack alice "mike jordan")
[root@localhost ~]# echo "${#array3[@]}"
4访问数组的第一个元素
[root@localhost ~]# echo "${array3[0]}"
tom
从索引1开始,向后访问
[root@localhost ~]# echo "${array3[@]:1}"
jack alice mike jordan
从索引1开始,向后访问2个元素
[root@localhost ~]# echo "${array3[@]:1:2}"
jack alice

6.数组案例

6.1 统计文件中男m,女f,小动物x 共有多少

// 常规linux命令统计, 统计男m,女f,动物x 共有多少

创建一份测试数据

# cat >> name-sex.txt <<EOF
NAME SEX
qwe m
zxc m
rty f
fgh f
zjz m
bnm f
gg x
asd m
mm x
EOF

awk:过滤第二行,sort:排序,uniq -c:用于统计数量

# awk -F' ' '{print $2}' name-sex.txt | sort |uniq -c3 f4 m2 x

// 使用数组统计, 统计男m,女f,小动物x 共有多少

[root@nothingzh shell]# bash array_tongji.sh
#!/bin/bash
#统计name_sex.txt 男m,女f,小动物x 共有多少
# v1.0 by zjz 2020-05-03#!!!统计字符必须使用到关联数组!!!
declare -A array_count
#1.对关联数组进行赋值
while read line
do#获取到name-sex.txt中的m、f、xsex=$(echo $line | awk -F' ' '{print $2}')let array_count[$sex]++  #当索引中m有一个是+1,
#       echo ${array_count[*]}done < /root/shell/name-sex.txt#2.对关联数组进行遍历循环,分别输出索引对应的内容
#	这里的i=m、f、x. ${!array_count[*]}用于取出索引
for i in ${!array_count[*]}
doecho "索引名称:$i,索引所对应个数:${array_count[$i]}"
done

查看执行过程

+ read line                  #读一行
++ echo qwe m                #此行是姓名:qwe  性别:m   
++ awk '-F ' '{print $2}'    #只取m
+ sex=m		                 #索引sex=m 赋值为m
+ let 'array_count[m]++'     #这里的array_count[m]++ 
++++ 		现在echo ${array_count[m]} ,它就等于1 
-------------
+ read line   
++ echo fgh f
++ awk '-F ' '{print $2}'
+ sex=f
+ let 'array_count[f]++'   #这里的array_count[f]++ 
+ read line
++++ 		现在echo ${array_count[f]} ,它就等于1 

整体执行结果:

[root@nothingzh shell]# bash array_tongji.sh
索引名称:f,索引所对应个数:3
索引名称:m,索引所对应个数:4
索引名称:x,索引所对应个数:2
6.2使用数组统计/etc/passwd中/bin/bash/bin/sync等等的用户有几个。

注意! /etc/passwd文件中的halt、shutdown用户。会导致在你不经意的情况下关机

测试脚本时不建议直接对/etc/passwd文件进行操作。
创建测试文件:
#cp /etc/passwd /root/

[root@localhost ~]# cat /root/passwd
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
..........

// 使用常规命令passwd文件的执行shell进行统计。

[root@localhost ~]# cat /root/passwd | awk -F':' '{print $NF}' | sort | uniq -c4 /bin/bash1 /bin/sync14 /sbin/nologin1 /sbin/qqqq1 /sbin/wwwwwww

//使用数组进行统计/bin/bash/bin/sync等等的用户有几个

[root@localhost ~]# cat array_count_bash.sh
#!/bin/bash
#用户/etc/passwd 文件/bin/bash等等的用户名有多少个
#v1.0 by zjz 2020-05-04#1.数组中的索引用到字符的,必须先声明为关联数组
declare -A array_count_bash
#2.对关联数组用的 索引  进行赋值
while read line
doshell=$(echo $line|awk -F':' '{print $NF}')let array_count_bash[$shell]++   #索引 赋值后 ++ 进行自加1
done < /root/passwd
#3. 对关联数组进行遍历循环,分别输出索引对应的内容
#$i = ${!array_count_bash[$i]} = /bin/bash /sbin/nologin 等等
#${array_count_bash[*]}  = 14 1 1 1 4
for i in ${!array_count_bash[*]}    #${!array_count_bash[*]}  !用于取得索引
doecho "suoying-name:$i ====> suoying-count:${array_count_bash[$i]}"
done

执行结果:

[root@localhost ~]# bash  array_count_bash.sh
suoying-name:/sbin/nologin ====> suoying-count:14
suoying-name:/sbin/qqqq ====> suoying-count:1
suoying-name:/sbin/wwwwwww ====> suoying-count:1
suoying-name:/bin/sync ====> suoying-count:1
suoying-name:/bin/bash ====> suoying-count:4
6.3 统计当前主机ss -an 的监听状态

//使用常规Linux命令统计
# ss -an | awk '{print $2}' | sort | uniq -c
执行结果

[root@localhost ~]# ss -an | awk '{print $2}' | sort | uniq -c91 ESTAB35 LISTEN1 State67 UNCONN

//使用关联数组统计当前主机ss -an 的监听状态

[root@localhost ~]# cat array_status.sh
#!/bin/bash
# 统计当前主机ss -an 的监听状态
# v1.0 by zjz 2020-05-05
#1.数组中的索引用到字符的,必须先声明为关联数组
declare -A array_state
#2.获取索引      统计的**对象**
state=$(ss -an | awk '{print $2}')
#3.对统计的**对象**进行累加
for i in $state
dolet array_state[$i]++
done
#4.对关联数组进行遍历循环,分别输出索引对应的内容
for j in ${!array_state[*]}
doecho "STATUS: $j, STATUS Count:${array_state[$j]}"
done

执行结果:#watch -n1 bash array_status.sh 每1s刷新一次结果

[root@localhost ~]# bash array_status.sh
STATUS: ESTAB, STATUS Count:91
STATUS: UNCONN, STATUS Count:67
STATUS: State, STATUS Count:1
STATUS: LISTEN, STATUS Count:35

运用实例:

  • 统计文件中男m,女f,小动物x 共有多少个
  • 统计/etc/passwd中/bin/bash/bin/sync等等的用户有几个。

总结上面实例 关联数组:
– 数组中的索引用到字符的,必须先声明为关联数组
– array_test={ [索引] =内容}
————————————–
* | @ 为访问所有元素
! 为获取数组的索引
—————————————
– 索引 统计的对象
– 内容 统计名称的数量
—————————————
– 处理文件对用while read line 取索引对象
– 程序运行结果用for进行循环统计

[root@localhost ~]# declare -A array_test
[root@localhost ~]# array_test=([zjz]=pt [zxy]=pt [xyx]=zz)
[root@localhost ~]# echo ${array_test[*]}
pt zz pt
[root@localhost ~]# echo ${!array_test[*]}
zjz xyx zxy

函数

完成特定功能的代码片段(块)
在shell中定义函数可以使用代码模块化,便于复用代码
函数必须先定义才可使用

通常用于:

  • 传参 $1,$2
  • 变量 local
  • 返回值 return $?

生活实例:
要生产三种水果汁,苹果、葡萄、橙汁
此时只需要一条流水线,送进去的是苹果,出来的就是苹果汁
函数=生产线
传什么参数=什么水果汁

语法结构

方法1:
函数名(){函数要实现的功能代码
}方法2:
function 函数名(){函数要实现的功能代码
}

例子1:计算阶乘 1乘到5的值

# vim sh/funtion_01.sh
#!/bin/bash
# 1x1 1x2 2x2 3x3 4x4
#v1.0 by zjz 2019.11.09
factorial () {   #定义一个函数名
factorial=1      #附一个初值,1,不然0乘以所有数都为0for ((i=1;i<=5;i++))  #C语言风格for i in `seq $1`     #shell脚本风格dofactorial=$[ $factorial * $i ] #公式done
echo "Factorial of 5 =$factorial" 
}
factorial        #最后要引用函数

执行结果:
在这里插入图片描述
例子2:通过用户自己输入要算的阶乘数

# vim sh/factorial-02.sh
#!/bin/bash
# 1x1 1x2 2x2 3x3 4x4
#v1.1 by zjz 2019.11.09
factorial () {
factorial=1#for ((i=1;i<=$1;i++))  #这个是函数内的$1为位置参数,跟下面的factorial $1相呼应dofactorial=$[ $factorial * $i ]done
echo "Factorial of $1 =$factorial"
}
factorial $1  #这个是factorial是引用定义的函数,$1是函数外的$1,就是我们命令后跟的数值

执行结果:
图中的5、6、7为函数外的$1
在这里插入图片描述
例子3:用户输入多个值,一次性计算

# vim sh/factorial-02.sh
#!/bin/bash
# 1x1 1x2 2x2 3x3 4x4
#v1.1 by zjz 2019.11.09
factorial () {
factorial=1for ((i=1;i<=$1;i++))dofactorial=$[ $factorial * $i ]done
echo "Factorial of $1 =$factorial"
}
factorial $1  #这里虽然是$1 $2 $3,但在函数看来都是$1
factorial $2
factorial $3

执行结果:

函数里的$1,是函数的第一个位置参数
函数外的$1,是脚本执行的第一个位置参数

在这里插入图片描述
shell脚本风格的写法:

# cat sh/factorial-03.sh
#!/bin/bash
# 1x1 1x2 2x2 3x3 4x4
#v1.1 by zjz 2019.11.09
factorial () {
factorial=1
#       for ((i=1;i<=$1;i++))for i in `seq $1`do#factorial=$[ $factorial * $i ]let factorial=$factorial*$idone
echo "Factorial of $1 =$factorial"
}
factorial $1
factorial $2
factorial $3

注意:
let不能加空格。let factorial=$factorial * $i ,这样写会报错
在这里插入图片描述
执行结果:
在这里插入图片描述

funtion函数的返回 return out

在函数里定义个公式:let 2*num,得出正确值

# bash return.sh  100
enter number: 111
fun2 return value: 0
[root@web1 script]# cat return.sh
#!/bin/bash
fun2 () {read -p "enter number: " numlet 2*$num}fun2
echo "fun2 return value: $?"

执行结果:
可以看出,返回值不是我们想要的222
在这里插入图片描述
自定义返回值:(最大值为255,超过)
将let 公式改为return
在这里插入图片描述
执行结果为
在这里插入图片描述
结论:

  • 函数默认最后一条命令的返回值作为函数返回值的状态码,也就是let 2*$num 的状态码返回值。
  • 成功既是:0,失败可能为非0的任一数字
  • 函数的返回值使用return的方式是有限制,超过255,计算结果就不正常

例子1:要点:函数返回大于255的值

函数的返回值大于255,就可使用out的方式输出返回值

使用result=`fun2`将函数的值输出给echo# bash return-out.sh
enter number: 111
fun2 return value: 222
[root@web1 script]# cat return-out.sh
#!/bin/bash
fun2 () {read -p "enter number: " numecho $[ 2*$num ]}result=`fun2`
echo "fun2 return value: $result"  #输出result的返回值

执行结果:此时就可输出大于255的值
在这里插入图片描述

函数传参

例子1:让用户输入三个位置参数,计算乘值

# cat parameter.sh
#!/bin/bash
#让用户输入三个位置参数,没输入则报错或提示输入
#v1.0 by zjz 2019-11-11if [ $# -ne 3 ];thenecho "usage: `basename $0` par1 par2 par3"exitfifun3() {#echo $[$1*$2*$3]echo "$(($1*$2*$3))"
}result=`fun3 2 3 4`
echo "result is :$result"

执行结果:
在这里插入图片描述
从程序执行的返回值来看,123,应该是6啊,为什么是24呢

因为是result=`fun3 2 3 4`,result直接将函数内的参数2、3、4赋值给fun函数就等于24

例子1.2:改进版

result=`fun3 $1 $2 $3`  #此时 result 读到的$1 $2 $3 读到的值为 $1=1 $2=2 $3=2

例子1.3:如果将result的值都改为$3 $3 $3 ,函数内得到得值为333=27
在这里插入图片描述

Published by

风君子

独自遨游何稽首 揭天掀地慰生平

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注