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
可以去掉斜线。
- 匹配文章中包含0,有2-3次的行
# grep "0\{3,4\}" num.txt
a\{n\}
重复至少n次,前一个重复的字符。
- 匹配文章中同时包含r两次的行
a\{,m\}
egrep
+
表示重复“一个或一个以上”前面的字符(*是0或多个)!
- ?表示重复“0个或一个”前面的字符
|
表示同时过滤多个字符串
)
分组过滤,后向引用。
#grep -E “gla|oo)d” zjz.log
awk
sed-增删改查(取行就用sed)
Linux系统:读取一行,操作一行
a/i – 增
- a 追加文本到指定行后
#sed '2a 106,dandan,CSO' person.txt
意思是在第2行后添加一行
- i 插入文本到指定行后
#sed '2i 107,dan,CSO' person.txt
在第1,2行中间添加一行
- 多行增加
#sed '2a 108,zjz,CCO\n109,zj,COC' person.txt
在第2行后添加 两行\n
为换行符)
- ssh优化
sed '20a Port 52113\nPermitRootLogin no\nPermitEmptyPasswords no\nUseDNS no\nGSSAPTAuthentication no' /etc/ssh/sshd_config
d-删
- nd 删除n行
# sed '2d' person.txt
- 删除文件内容但其中去除oldboy的行
[root@oldboy ~]# sed '/oldboy/d' person.txt # ——→删除包含"oldboy"的行
102,zhangyao,CTO
103,Alex,COO
104,yy,CFO
105,feixue,CIO
c-改
按行替换,用新行取代旧行
- 将文件中的第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
- 替换文章中的某一个单词
[root@localhost ~]# sed 's#oldboy#zjz666#g' person.txt
101,zjz666,CEO
102,zhangyao,CTO
103,Alex,C00
104,yy,CFO
105,feixue,CI0
- 指定行修改配置文件 CTO原来,CCO现在
p – 查
输出指定内容,但默认会输出2次匹配的结果,因此使用n取消默认输出
测试文件内容
# cat test/num.txt -n1 120002 4230003 57300004 0001235 0007893436 1111
- 按行查询
[root@oldboy ~]# sed -n '2p' person.txt
102,zhangyao,CTO
- 按范围查询从第1行开始隔两行输出一次)
# sed -n '1~2p' test/num.txt
12000
5730000
000789343
- 按字符串查询
# sed -n '/123/p' test/num.txt
000123
- 混合查询
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
- 查看索引下标
#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