3-Linux脚本

本文详细介绍了Shell脚本的基础知识,包括变量、参数传递、运算符、控制结构(如if/for/while/until/case)、函数以及数组的使用。通过示例展示了如何创建和使用Shell脚本,例如使用while读取文件内容、数组统计、函数返回值以及文件系统管理。此外,还探讨了Shell脚本中的一些实用技巧,如错误处理和条件测试。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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/bash

echo "-- \$* 演示 ---"
for i in "$*"; do
    echo $i
done

echo "-- \$@ 演示 ---"
for i in "$@"; do
    echo $i
done

执行结果
# ./test.sh 1 2 3
-- $* 演示 ---
1 2 3
-- $@ 演示 ---
1
2
3

参数实例

# vim canshu.sh
#!/bin/bash

echo "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;ls
 16: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 w

check        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 -n
     1  12000
     2  423000
     3  5730000
     4  000123
     5  000789343
     6  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 ];then
    echo "ping正常,网络ok"
else
    echo "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 ];then
   echo "vsftpd 未安装"
   yum install vsftpd
fi

   systemctl restart vsftpd
   ss -tnlp | grep "vsftpd" >>/dev/null
if [ $? -eq 0  ];then
   vsftpd_address=$ip
   vsftpd_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"

else
   echo "vsftpd服务器未启动"
fi

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

例子3.判断用户输入的是否是数字
#!/bin/bash
#判断输入的是否是数字
#v1.1 by zjz 2019-10-30

read -p "请输入字符:" num

if [[ "$num" =~ ^[0-9]+$ ]];then
  echo "你输入的是数字"
else
  echo "你输入的不是数字"
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 httpd
        systemctl enable httpd
        #判断防火墙是否开启
        ps -aux | grep firewalld | grep -v 'color' &> /dev/null
        if [ $? -eq 0 ];then
            firewall-cmd --permanent --add-service=http
            firewall-cmd --permanent --add-service=https
            firewall-cmd --reload
            echo "Firewalld strategy alread update"
        else
            echo "Firewalld not running"
        fi
        
        #判断selinux是否开启
        getenforce | grep enfor
        if [ $? -eq 0 ];then
            sed -i 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config
            setenforce 0
        else
            echo "SELinux not running"
        fi
        #curl http://127.0.0.1
elif    ping -c1 $gataway &>/dev/null;then  #ping网关,若是通,可能就是dns问题
        echo "check DNS"
else
        echo "检查IP配置是否正常"   #如果都排查不了就手动检查网络配置
        exit
fi

for

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

语法结构
for 变量名(如:i)   in  [ 取值列表 ]
do
   循环体
done

C语言:
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.$i
        ping -c1 -W1 $ip &>/dev/null  #ping1次,每次1s
        if [ $? -eq 0 ];then   # $?判断是否ping通
                echo "$ip is up" >> `date +%F`ip-up.txt  # ping成功,写入文件
        else
                echo "$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.txt

echo "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作为取值条件
do
        ping -c1 -W1 $ip &>/dev/null
        if [ $? -eq 0 ];then
                echo "$ip up"
        else
                echo "$ip down"
        fi
done

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

例子3:使用for循环,批量创建用户 前缀+序号,并添加密码
# cat create_user.sh
#!/bin/bash
#批量创建用户并设置密码
#v1.0 by  zjz 2019-11-03
while true
do
	read -p "Please enter prefix & passwod & num [ai 123 5]: " prefix pass num 
	#理解为:username=prefix+num  prefix:前缀  pass:密码	 num:创建多少人用户
	printf "user information:   #将用户输入的值打印出来
	        ------------------
	        user prefix: $prefix
	        password:$pass
	        num:$num
	        -----------------
	"
	read -p "Are you sure ?[y/n]: " action   
	        if [ $action = "y" ];then
	                break
	        fi
done

for i in `seq -w $num` 
do
        user=$prefix$i
        id $user &>/dev/null
        if [ $? -eq 0 ];then    #检测用户是否已存在
                echo "useradd: user $user already exists"
        else
                useradd $user
                echo "$pass" | passwd --stdin $user &>/dev/null
                if [ $? -eq 0 ];then
                        echo "$user is created"
                fi
        fi
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-04

for ip in `cat ip-aliving.txt | awk '{print $1}'`  #找到需要修改的IP 
do
        {
                ping -c1 -W1 $ip &>/dev/null
                if [ $? -eq 0 ];then
                        ssh $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配置文件,改为disabled
                        ssh $ip "systemctl stop firewalld"                                           #关闭防火墙
                        ssh $ip "setenforce 0"                                                       #关闭selinux
                fi

        }&    #后台运行,加速运行
done
wait
echo "config ok..."

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

例子5:for 计算从1加到100
#!/bin/bash
# sum 1++100=??
#v1.0 by zjz 2019-11-05

for i in {1..100}  #i=1一直循环到100结束
do
        let 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`
do
        for j in `seq 1 $i`
        do
        echo -n -e "${i}x${j}=$[$i*$j]\t"
        done
        echo
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-04

while read line
do
        user=`echo $line | awk '{print $1}'`
        pass=`echo $line | awk '{print $2}'`
        id $user &>/dev/null
        if [ $? -eq 0 ];then
                echo "user '$user' already exists"
        else
                useradd $user
                echo "$pass" |passwd --stdin $user
                if [ $? -eq 0 ];then
                        echo "$user is create"
                fi
        fi
done < $1  #读入命令行后的一个参数,导入while循环
例子3:V2 解决文件里有 空行、空格
[root@localhost sh]# cat while-create-user.sh
#!/bin/bash
# while create user
#v1.0 by zjz 2019-11-04

while read line
do
        if [ ${#line} -eq 0  ];then   # ${#line} 用于判断当前行长度是不是0
                 #exit    #直接退出程序
                #break   #退出当前while循环
                continue #退出当前行,继续往下执行下一行
        fi
        user=`echo $line | awk '{print $1}'`
        pass=`echo $line | awk '{print $2}'`
        id $user &>/dev/null
        if [ $? -eq 0 ];then
                echo "user $user already exists"
        else
                useradd $user
                echo "$pass" |passwd --stdin $user
                if [ $? -eq 0 ];then
                        echo "$user is create"
                fi
        fi
done < $1

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

例子4:测试主机的连通性,条件为真,能ping通主机,执行while循环体(下线)
[root@localhost sh]# cat while-conn-test.sh
#!/bin/bash
#while ping host
#v1.0 by zjz 2019-11-05

ip=192.168.11.137
while ping -c1 -W1 $ip &>/dev/null  #主机能ping通,条件为真,就进入while循环
do
        sleep 1   #停个1s
        break
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-05

i=1   #赋初值i=1
while [ $i -le 100 ] #当i小于100时,条件为真,执行循环
do
        let 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
do
        sleep 1
        break
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-05

i=1   #赋初值i=1
until [ $i -gt 100 ] #i大于100,条件为假,执行循环
do
        let sum=$sum+$i  #let运算,sum=sum+i
        let 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/null

case "$os_version" in
7)
  curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo
  curl -o /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo
  if [ $? -ne 0 ];then
     echo "Aliyun-YUM仓库安装失败,请检测网络连接、DNS是否正常"
     exit
  fi
  ;;

6)
  curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-6.repo
  curl -o/etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-6.repo
  if [ $? -ne 0 ];then
     echo "Aliyun-YUM仓库安装失败,请检测网络连接、DNS是否正常"
     exit
  fi
  ;;

esac
例子2:请用户确认要删除的用户,case判断
# cat del-user.sh
#!/bin/bash
#----------------
#del user
#v1.0 by zjz 2019-10-29
#---------------
read -p "请输入要删除的用户: " duser
id $duser  &>/dev/null

if [ -z $duser ];then
  read -p "请输入要删除的用户: " duser
fi

if [ $? -ne 0 ];then
  echo "您输入的用户$duser不存在"
  exit 1   #用户不存在返回值设置为1
fi

read -p "你确定要删除此用户$duser吗?[y/n]:" action
case $action in
y|yes|Y|YES)       #case条件可以写或y或yes或Y或YES
  userdel -r $duser
  echo "$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-30

menu() {         #这段函数作用是 实现重复调用打印菜单,使代码变得整洁
        cat <<-EOF
        #-------系统管理工具箱----------
        #       h.帮助                 #
        #       f.磁盘分区             #
        #       d.文件系统挂载情况     #
        #       m.内存使用情况         #
        #       u.系统负载             #
        #       q.离开                 #
        #-------------------------------
        EOF
}
menu    #此menu,当用户执行脚本时,先打印上方的菜单

while true   #进入死循环
do
        read -p "请输入[h for help]: " action

        case "$action" in   #判定用户输入的action
        h) 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
do
                hosts_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 索引下的内容
do
        echo "索引是:$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 -c
      3 f
      4 m
      2 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、x
        sex=$(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[*]}
do
        echo "索引名称:$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 -c
      4 /bin/bash
      1 /bin/sync
     14 /sbin/nologin
      1 /sbin/qqqq
      1 /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
do
        shell=$(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[*]}  !用于取得索引
do
        echo "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 -c
     91 ESTAB
     35 LISTEN
      1 State
     67 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
do
        let array_state[$i]++
done
#4.对关联数组进行遍历循环,分别输出索引对应的内容
for j in ${!array_state[*]}
do
        echo "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乘以所有数都为0
        for ((i=1;i<=5;i++))  #C语言风格
        for i in `seq $1`     #shell脚本风格
        do
                factorial=$[ $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相呼应
        do
                factorial=$[ $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=1
        for ((i=1;i<=$1;i++))
        do
                factorial=$[ $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*$i
        done
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: " num
        let 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: " num
        echo $[ 2*$num ]

}

result=`fun2`
echo "fun2 return value: $result"  #输出result的返回值

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

函数传参

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

# cat parameter.sh
#!/bin/bash
#让用户输入三个位置参数,没输入则报错或提示输入
#v1.0 by zjz 2019-11-11

if [ $# -ne 3 ];then
        echo "usage: `basename $0` par1 par2 par3"
        exit

fi

fun3() {

        #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
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值