Linux——shell 脚本入门基础知识 到 实战☆☆☆☆(变量、判断、循环、数组和函数、三剑客)

本文目录

第一章 变量

1.前言

2.自定义变量

3. 整数运算

4. 小数运算

5.环境变量

5.1 位置变量

5.2 预定义变量

第二章 判断

1. shell 条件测试

1.1 数值比较

1.2 文件测试

1.3 字符串比较

1.4 and 和 or

2.流程控制:if

2.1 单分支结构

2.2 双分支结构

2.2 多分支结构

2.3 嵌套结构

2.4 调试脚本

2.5. 总结(注意)

3.模拟匹配:case

3.1 前言

3.2 案例1:简单的模式匹配

3.3 案例2:简单的Jump server

第三章 循环

1. shell循环:for

1.1 语法结构

1.2 案例1 :ping测试主机

1.3 案例2:通过用户列表文件创建用户

1.4 案例3:使用for实现批量主机root密码的修改

2. Shell循环:while until

2.1 while 语法结构

2.2 until 语法结构

3.3 退出程序或者循环的三种方式

3. expect

3.1语法结构

3.2 示例1:通过expect解决ssh交互问题

3.3 示例2:expect实战:公钥推送

第四章 数组和函数

1. 数组

1.1 简介

1.2 类型

1.3 普通数组

1.4 关联数组

1.5 数组和循环的案例1

1.6 数组和循环的案例2

2. 函数               

2.1 概述

2.2 案列1:定义数组和应用数组

2.3 案列2:利用传参把数据应用到函数里

2.4 案列3:阶乘概念

2.5 案列4:数组传参,函数结果再赋予数组

3. 影响shell程序的内置命令

3.1 概述

3.2 shift 案列

第五章 三剑客

1. 正则表达式

1.1 名词解释

1.2 案例

1.3 元字符

2. grep

3. sed

3.1 前言

3.2 格式

3.3 返回值

3.4 sed和正则表达式

3.5 汇总示例 重点

  4. awk

4.1 前言

4.2 工作原理

4.3 语法

4.4 内部变量

4.5 格式化输出

4.6 模式(正则表达)和动作

4.7 awk脚本编程

4.7.3 循环

4.7.4 数组

4.7.5 awk编程案例


第一章 变量

1.前言

什么是shell ==> shell,壳,命令解释器,一种应用程序。

shell变量? 用一个固定的字符串去表示不固定的内容,便于修改。

2.自定义变量

  • 定义变量:只需执行 “变量名=变量值” 命令即可,要遵守变量定义规则,

                         例如:#name="xielei666"

  • 变量调用:定义变量name:#name="xielei666"

                         输出变量name的值:#echo_$name

  • 重复定义变量:

   ①重复定义变量会进行覆盖,例如#aa=123,再aa=456,最后查询 echo=$aa,结果会显     示456 

   ②重复定义变量aa的值是原aa的值加上456,可以这样做:aa="$aa"456 或者             aa=${aa}456

  • 变量查看: 已知变量名可通过echo来查询变量值,如果不知变量名,可通过 #set命令 来查询系统中已经存在的变量
  • 变量删除:#unset_aa ,aa代表变量名,unset中文复位

  •  变量示例:①使用变量前:

                           编写测试主机在线的脚本,在线时提示在线,不在线时提示不在线。

                          编写脚本如下:       

vim ping.sh
#!/bin/bash
ping -c1 192.168.145.227 &> /dev/null && echo 192.168.145.227up || echo 192.168.145.227down
#c1指ping的次数,&>指输出到,/dev/null指垃圾桶,&& echo 192.168.145.227up 指前面的命令成功了会提示这一句,|| echo 192.168.145.227own指前面的命令失败了会提示这一句      

                        ②使用变量后:

                         编写测试主机在线的脚本,在线时提示在线,不在线时提示不在线。

                         编写脚本如下:

vim ping1.sh
#!/bin/bash
ip=192.168.145.227
ping -c1 $ip &> /dev/null && echo "$ip"up || echo ${ip}down
  • 交互定义变量:read从键值读入变量值,赋值方式:read 变量名

示例:

vim ping2.sh
#!/bin/bash
read -p "请输入您需要测试的第1个IP: " ip1     # "请输入您需要测试的第1个IP: "这个是显示给客户看的内容
read -p "请输入您需要测试的第2个IP:" ip2
ping -c1 $ip1 &> /dev/null && echo "$ip1"up || echo ${ip1} down
ping -c1 $ip2 &> /dev/null && echo "$ip2"up || echo ${ip2} down  

3. 整数运算

①方法1:expr

语法:
#expr 1 + 2
#expr $numl + $num2
案列:需求是运算学员的总分
vim sum.sh
#!/bin/bash
read -p "请输入您的第一门成绩: " number1
read -p "请输入您的第二门成绩: " number2
echo -n "总成绩为: " 
expr $number1 + $number2

②方法2:$(( ))

语法:
echo $((2**3))  #指2的3次方
echo $((num1+num2))
echo $((2+2))

③方法3:$[ ]

语法:
echo $[5+2]
echo $[5**2]

④方法4:let(运算)

示例:
let sum=2+3;echo $sum
let i++;echo $i  #这个方法一般是用来计数的

4. 小数运算

请提前安装计算器程序 bc

示例:
echo "2+4" | bc
echo "scale=2; 10/3" | bc   # scale=2指小数位为2位

5.环境变量

5.1 位置变量

命令  参数1 参数2 参数3 ...... 以后特殊定义

           $1      $2       $3    ......

示例:需求:制作脚本,用户自动输入两门学科成绩,自动算出平均分

vim avg.sh
#!/bin/bash
echo "($1+$2)/2" | bc

再测试脚本
# bash avg.sh 50 60 

这里执行脚本命令后面跟的两个参数,分别是 $1 和 $2

5.2 预定义变量

语法: 
$0   脚本名/程序名
$*   所有的参数
$#   参数的个数   # 有变量的话,就要这样写${#file} ,file指变量名
示例:
vim avg1.sh
#!/bin/bash
echo "($1+$2)/2" | bc
echo "该程序名为$0"
echo "该程序使用了$#个参数"
echo "该程序使用了以下参数(所有参数)$*"
$? 上一个程序的返回值(0是成功,非零即失败)
$$ 程序的PID (可以区分进程,父子进程)

第二章 判断

1. shell 条件测试

===数值比较 [ 整数1 操作符 整数2 ]

===文件测试 [ 操作符 文件或目录 ]

===字符串比较 [ “字符串” = “字符串1” ]

and 和 or

1.1 数值比较

格式:[ 整数1 操作符 整数2 ]

①操作符:

  • [ 20 -gt 10 ] 大于  
  • [ 1 -lt 10 ] 小于
  • [ 1 -eq 1 ] 等于
  • [ 1 -ne 10 ] 不等于
  • [ 20 -ge 10 ] 大于等于
  • [ 10 -le 10 ] 小于等于

左边数值:客户输入值      右边数值:自定义值

②示例

需求:猜测用户输入的密码,是否满足长度需求

脚本:

vim  pass.sh           #输入以下内容:
#!/bin/bash           #脚本声明                                    
read  -p  "请输入您的密码:  "      pass   
if    [  ${#pass}    -lt  7   ]                       
then   echo   "您的密码太短(输入正确)"  #then意思是if命令返回值0,则显示该命令                  
else   echo  "您的密码太长"        #else意思是if命令返回值是1,则显示该命令                 
fi             #结束

1.2 文件测试

格式:[ 操作符 文件或目录 ]

①操作符

-f

file

当file存在时返回真

-b

file

当file存在并且是个文件时返回真(返回0)

-d

pathname

当由pathname存在并且是一个目录时返回真

-e

pathname

当由pathname指定的文件或目录存在时返回真

-w

pathname

当由pathname指定的文件或目录存在并且可写时返回真

-x

pathname

当由pathname指定的文件或目录存在并且可执行时返回真

-pfilename

当filename存在并且是命名管道时返回真

                 .............

②示例

需求:请用户输入备份的目录,

如果目录存在提示已存在可以备份,如果不存在设置新建目录,并提示创建成功可以备份。

脚本:

vim  path.sh      #输入以下内容
#!/bin/bash
read   -p   “请输入要备份的目录”   path     #path是变量名
if  [    -d    $path   ]      # -p是操作符,[]是测试语法
then    echo   "$path存在且可以备份"
else    echo  "$path不存在不能备份"
mkdir  "$path"
echo  "$path目录已创建,可以备份"
fi

最后,利用bash 执行该变量验证结果

bash  path.sh 

1.3 字符串比较

格式:[ “字符串” = “字符串1” ]

①操作符:

= , 等于                          -z , 判断字符长度是为0 (了解)

!= ,不等于                    -n , 判断字符长度不是为0 (了解)

②示例:

需求:邀请用户确认,yes升级装备,no不升级装备

脚本:

vim  select.sh     
#!/bin/bash     
read -p "您确认要升级这件装配吗?[yes/no] "   select   
if  [ "$select" = "yes" ]   # 两个双引号最好加上,保证不报错
then  echo "装配开始升级"
else  echo "感谢您,欢迎下次光临"
fi

如果条件测试想设为不等于就加上 ,比如

if  [ "$select" != "yes" ]   # != 为不等于 

1.4 and 和 or

①简介

当条件测试比较复杂时,需要多个条件同时成立。就需要混合条件测试了。

逻辑的(and)和(or):

&&  逻辑的 and 的意思,         -a , 两个条件同时成立,为真

 ||    逻辑的 or 的意思,            -o ,两个条件一个成立,为真

②示例:

需求:猜测用户输入的密码是否满足如下条件

1.长度大于7位

2.包含字母大写

3.包含字母小写

4.包含符号“ @ _ ! ”

脚本:

vim user.sh
#!/bin/bash
read -p "请输入您的密码: "   pass
if [ ${#pass} -gt 7 ] && [[ ${pass} =~ [a-z] ]] && [[ ${pass} =~ [A-Z] ]] &&  [[ ${pass} =~ [@_!] ]]  #使用特殊符号~(指区间)时就要加双括号 
    then 
          echo "yes"
    else
          echo "no" 
fi

如果使用逻辑中的or ,也就是 || ,那么其中的要求只要满足一个即可

vim user.sh
#!/bin/bash
read -p "请输入您的密码: "   pass
if [ ${#pass} -gt 7 ] || [[ ${pass} =~ [a-z] ]] || [[ ${pass} =~ [A-Z] ]] || [[ ${pass} =~ [@_!] ]] 
    then 
          echo "yes"
    else
          echo "no" 
fi

2.流程控制:if

2.1 单分支结构

语法:

if [ command/test ] ; then

      符合该条件执行的语句

fi

2.2 双分支结构

语法:

if      条件测试    # if后面跟条件测试或命令
then   命令序列     #满足条件测试就会执行该命令 
else    命令序列    #不满足条件测试就会执行该命令
fi

2.2 多分支结构

①语法:

if      条件测试1
then    命令序列
elif    条件测试2  #多分支就是在then和else之间插入一个elif
then    命令序列
elif    条件测试3   #可以根据需要插入多个elif
then    命令序列.....
else    命令序列
fi

②示例

需求:编写脚本,取出系统时间的小时,对数字进行判断,并进行播报

6-10 点,播报this is morning

11-13点,播报this is noon

14-18点,播报this is afternoon

其它时候,播报this is night

脚本:

vim  time.sh
#!/bin/bash
hour=`date +%H`
if [ $hour -ge 6 -a  $hour -le 10 ]
    then
    echo "this is morning"
    elif [ $hour -ge 11 -a $hour -le 13 ];then
    echo "this is noon"
    elif [ $hour -ge 14 -a $hour -le 18 ];then
    echo "this is afternoon"
    else
    echo "this is night"
fi

2.3 嵌套结构

①语法:嵌套结构只需要在需要的地方,直接在加上一个 if 即可 。


if  条件测试1  then 命令序列
     if 条件测试1  then 命令序列
     else 命令序列
     fi
else 命令序列
fi

②需求

脚本:

vim user.sh
#!/bin/bash
#time 20230110
#description
#邀请用户输入待创建的用户名
read -p "请您输入要创建的用户名: "   user
#判断用户名是否存在
if id $user
      then 
      echo "${user}已经存在"
      else 
      read -p "请您输入用户密码" pass
           if [ ${#pass} -gt 7 ]      #井号一定要加,代表该参数的个数
           then              #useradd $user 新建用户该命令也可以放在这一行
           echo $pass | passwd --stdin $user
           useradd $user #新建用户该命令不能放在上到的else里,因为程序是从上往下执行的,就算后面密码输入不对,但该用户已经被创建了,后面再创会报错
           echo "${user}用户创建成功,密码为:$pass "
           else
           echo "该密码不符合要求"   
           fi
fi

2.4 调试脚本

①语法:

#bash -n user.sh   #-n指仅调试脚本中的语法错误,user.sh是一个脚本名  
#bahs -vx user.sh  #-vx指以调试的方式执行脚本,查询整个执行过程

2.5. 总结(注意)

  •  [ ] 表示条件测试。注意这里的空格很重要
  •  在shell中,then 和 if 是分开的语句。如果要在同一行里输入,则需要用分号将它们隔开
  •  注意 if 判断中对于变量的处理,需要加 “ ”{ } ,以免一些不必要的错误
  •  使用 -z 或者 -n 来检查长度的时候,没有定义的变量也为0

3.模拟匹配:case

3.1 前言

shell 编程中 if 和 case 都是用来做流控的

case 语法结构:

case 变量 in
模式1)
命令系列1
;;
模式2)
命令系列2
;;
*)
无匹配后命令序列
esac

3.2 案例1:简单的模式匹配

①需求:邀请用户输入待删除用户名

询问用户,确认要继续删除吗 ? yes/no

②脚本:

vim  userdel.sh
#!/bin/bash
read -p "请您输入需要删除的用户名: "  user
read -p "确认吗?[yes/no]"   action
case $action in
yes|y|YES|Yes)
userdel -r $user
echo "$user 已删除"
;;
*)            # 这里的*代表所有。除了上面的参数外,输入任意内容都会执行下一行命令
echo "感谢下次光临,再见"
;;
 esac

3.3 案例2:简单的Jump server

①需求:

  • 由于工作中,我们需要管理多台服务器。那么我们访问服务器就是一件繁琐的事情。
  • 通过Shell编程,编写跳板程序。当我们需要访问服务器时,看一眼服务器列表名,按一下数字,就可以直接登录成功。

②脚本:

vim jump.sh
#!/bin/bash
#定义目标主机ip
web1=192.168.145.170
web2=192.168.145.171
mysql=192.168.145.172
#打印跳出菜单
cat <<EOF                    #EOF 指打印,内容结束后要跟一个EOF
请选择需要登录的服务器序号
1.登录web1服务器
2.登录web2服务器
3.登录mysql服务器
4.按任意键退出
EOF
read -p "您的选择是: " number
case $number in
1)
ssh root@$web1
;;
2)
ssh root@$web2
;;
3)
ssh root@$mysql
;; 
*)
echo "感谢下次光临,再见"
exit
eac

第三章 循环

for循环 :是用空格和回车作为分隔符

while循环: 是用回车作为分隔符

1. shell循环:for

循环嵌套的规则是:外部循环一次,内部循环全部。

echo一次就是一个回车,不管后面跟不跟输出内容

echo后面跟-n :代表取消echo输出后的回车动作

1.1 语法结构

for 变量名 [ in 取值列表 ]   #for 加上 变量名 加上 循环范围
do
循环体     #需要循环的内容
done

1.2 案例1 :ping测试主机

思路:编写常规ping测试脚本(无循环)

添加循环语句,for i in {1..10}

优化脚本(后台执行,清空脚本,循环完成提示,wait间隔)

脚本:

vim ping.sh
#!/bin/bash
for i in {1..254}
do
  {
   ip=192.168.145.$i
   ping -c1 -W1 $ip &> /dev/null  # -c1指ping一次,-W1指等待时间只等1秒
       if [ $? -eq 0 ]    # &?指上一个程序的返回值。-eq指等于
       then 
      echo "$ip" | tee -a ip.txt  #| tee 指前面的id 会同时显示在屏幕上和ip.txt这个文件里。也可以换成> ip.txt,只放在文件里。-a 加上是追加,不加上则覆盖。
      fi
  } &       # {} & 指把其中的命令丢到后台去执行
done
wait   # 分割、间隔的意思
echo "完成"

1.3 案例2:通过用户列表文件创建用户

①需求1:

通过人事提供的人员名单,以用户名来创建用户

#!/bin/bash
for user in `cat user1.sh`   #for循环是用空格和回车作为分隔符
do
  useradd $user
  echo "${user}用户创建成功"
done

②需求2:

升级:用户可以使用参数的形式,自定义用户名文件

如果用户没有输入用户名文件,提示用户输入

如果用户输入的不是有效文件,提示用户更正

启动循环创建用户

如果用户已经存在,提示存在

如果用户不存在,则创建成功,并提示成功

#!/bin/bash
read -p "请输入需要创建用户的用户名文件: "  file
if [ ${#file} -eq 0 ]  
  then 
      echo "请输入用户名文件"
      exit 88
fi     
if [ ! -f $file ]   # !和-f 之间一定要有空格才行
  then
      echo "您输入的不是有效文件"
      exit 99
fi        
for user in `cat $file`     # `  ` 和 $()  意思一样,都是先执行的意思
do                  #循环开始
if  id $user   &> /dev/null  #条件测试才需要加[],命令不需要加[]
  then
      echo "${user}用户已经存在":
  else
      useradd  $user
      echo "${user}用户创建成功"
fi                  
done                #循环结束

1.4 案例3:使用for实现批量主机root密码的修改

①需求:

使用for实现批量主机root密码的修改

前提1:已经完成秘钥登录配置 (ssh-keygen)

ssh-keygen   #现在本机生成秘钥
shh-copy-id 192.168.145.170  #把生成的秘钥复制到需要改密码的机器上

前提2:定义主机地址列表

vim ip.txt
192.168.145.170
192.168.145.171
192.168.145.172

前提3:并了解远程修改密码的方法

②脚本:

#!bin/bash
read -p "请输入用户密码: " passwd
for i in $(cat ip.txt)              # $() 和 `  ` 意思一样,都是先执行的意思
do
{
ping -c1 -W1 $i &> /dev/null
if [ $? -eq 0 ] 
then
    ssh $i "echo $passwd | passwd --stdin root"   #ssh后面加不加root都可以,默认是root 
      if [ $? -eq 0 ] ;then   
         echo "$i" >> ok.txt        #如果改成功就把当前IP记录到ok.txt文件里
       else 
         echo "$i" >> fail.txt      #如果改失败就把当前IP记录到fail.txt文件里
     fi
else  
echo $i >> meigai.txt         #如果没ping成功就把当前IP记录到meigai.txt文件里
fi
} &
done
wait   # 分割、间隔的意思
echo "完成"

2. Shell循环:while until

2.1 while 语法结构

while : 条件测试  #当条件测试成立(条件测试为真),执行循环体。 : 冒号代表为真
do
循环体         
done

①示例:

每秒显示一个数字,一次递增+1

while :
do
let i++  
sleep 1    #每一秒数一次
echo $i
done    

增加需求:数字数到8结束

while :
do
let i++
   if [ $i -eq 8 ] ;then
      exit
   fi   
sleep 1
echo $i
done

2.2 until 语法结构

until 条件测试      #当条件测试成立(条件测试为假时视为成立),执行循环体
do
循环体
done

①示例

until  [[ $i -eq 14 ]]
do
let i++         #let 可以帮我数数
sleep 1
echo $i
done

3.3 退出程序或者循环的三种方式

命令解释

exit

退出整个程序

break

退出当前循环程序

continue

退出此次循环,进行下一次循环

3. expect

名词解释:

expect:期待,希望的意思。

我们通过shell 可以实现简单的控制流功能,如循环、判断等。但是对于需要交互的场合则必须通过人工来干预,有时候会需要实现和交互程序如ssh服务器等进行交互的功能。而expect就可以帮我们实现功能进行自动交互

3.1语法结构

spawn  命令         #spawn是expect内部命令,启动一个shell程序
expect {
"问什么" { send " 答什么" }
"问什么" { send  "答什么" };
}

3.2 示例1:通过expect解决ssh交互问题

先安装该程序

yum -y install expect tcl tclx tcl-devel

需求:

通过expect解决ssh交互问题

脚本:

#!/usr/bin/expect
spawn ssh root@192.168.145.170   #spawn是expect内部命令,启动一个shell程序
expect { 
 "yes/no" { send "yes\r";exp_continue }   #exp_continue指这一行如果没有问,请继续下一条,忽略这一条
 "password:" { send "666666\r" };   #send指发送 ,\r指回车
 }
 interact    #交互的意思,指执行完上面后,请停留在交互界面

3.3 示例2:expect实战:公钥推送

准备工作:安装expect,准备公钥

①需求:

通过shell循环判断在线主机

通过expect进行交互

优化脚本

②脚本:

#!/bin/bash
>ip.txt      #新建一个文件
for i in {2..254}
do
{
ip=192.168.145.$i
ping -c1 -W1 $ip &> /dev/null
 if  [ $? -eq 0 ] ;then
    echo $ip >> ip.txt     

/usr/bin/expect  <<EOF              #放到if后面可以搭上高平列快车上    
   set timeout 10                    #最多执行10秒
   spawn ssh-copy-id $ip
   expect {
  "yes/no" { send "yes\r";exp_continue }
  "password:"  { send "666666\r" };
          }  
expect eof                         # 表示结束expect ,退出  
EOF       #EOF的后面一定不能有空格、字符,不然报错(检测了一晚上才发现这个问题,切记)

fi    
} &
done
wait
echo "完成"

第四章 数组和函数

1. 数组

1.1 简介

变量:用一个固定的字符串,代替一个不固定字符串

数组:用一个固定的字符串,代替多个不固定字符串

1.2 类型

普通数组:只能使用整数作为数组索引

关联数组:可以使用字符串作为数组索引

1.3 普通数组

①定义数组

方法一:一次赋一个值

数组名[ 下标 ]=变量值
#xielei[0]=yasuo
#xielei[1]=timo
#xielei[2]=dema

方法二:一次赋多个值

xielei=(yasuo timo dema)
xielei=(`cat/etc/passwd`) #将该文件中的每一行作为一个元数赋值给数组xielei
xielei=(yasuo timo "dema dage") # 一个值中间有空格等特殊字符要加双引号
xielei=($a $b) #也可以是两个变量值

②查看数组

echo ${xielei[*]}   #指查看xielei数组里的所有内容
echo ${!xielei[*]}  #加!代表查看xielei数组里的所有索引
echo ${xielei[1]}   #1指仅查看1索引上面的内容
echo ${xielei[*]:1:2}  #从数组下表的1开始,访问两个元素
echo ${xielei:3}  #指查看数组下表的3开始,访问后面所有内容

1.4 关联数组

①定义关联数组

首先要先声明关联数组

declare -A xielei  # -a指查看所有关联数组 , declare -A指声明,xielei指变量名

方法一:一次赋一个值

declare -A xielei
xielei[index1]=yasuo
xielei[index2]=tomo
xielei[index3]=dema

方法二:一次赋多个值

xielei=([index1]=yasuo [index2]=tomo [index3]=dema [index4]="dema dage")

1.5 数组和循环的案例1

需求:通过循环定义和显示数组

通过数组统计数据

脚本:

#准备一个sex.txt文档
vim sex.txt
zhangsan 男  北京
lisi     男  北京
xiaoli   女  北京
#!/bin/bash
declare -A xielei       # 声明xielei是关联数组
while read fenge        #从sex.txt读取内容,定义变量为fenge
do
type=`echo "$fenge" | awk '{print $2}'`  # awk {print } 指会把一行的内容以空格分隔开,$2指分隔后的索引(列)
let xielei[$type]++    # xielei[$type] 相当于i的意思,意思是读取一个一样的索引会在后面+1,m+1+1、y+1
done < sex.txt         #从sex.txt读取内容
for i in ${!xielei[*]}   #上面的while循环结束后才会执行下面的for循环
do
echo "索引 $i : 值是 ${xielei[$i]}"
done

脚本执行后结果如下, 统计出了sex.txt文件中 “每个性别” 对应的人数

[root@localhost ~]# bash dalace.sh 
索引 男 : 值是 2
索引 女 : 值是 1
[root@localhost ~]# 

1.6 数组和循环的案例2

需求:使用数组统计用户shell的类型和数量

先查看系统用户类型

cat /etc/passwd

显示结果为:shell的类型在第7列

liwenguo:x:1529:1532::/home/liwenguo:/bin/bash
xielei:x:1530:1533::/home/xielei:/bin/bash
unbound:x:993:988:Unbound DNS resolver:/etc/unbound:/sbin/nologin

脚本:用while循环 :while循环是以换行符号来读取内容(一行一行的读取内容)

#!/bin/bash
declare -A shells
while read file
do
type=`echo $file | awk -F: '{print $7}'`  # -F指的是可以指定分隔符号是冒号,一般默认分隔符号是空格
let shells[$type]++
done < /etc/passwd
for i in ${!shells[*]}
do
echo "索引/shell类型:$i , shell数量是: ${shells[$i]}"
done

脚本:用for循环:for循环是以空格和换行符来读取内容(一个空格一个空格的读取内容)

#!bin/bash
OLD_IFS=$IFS        #先备份老的分隔符
IFS=$'\n'            # $\n表示换行符,重新定义它
declare -A shells
for i in `cat /etc/passwd`
do
type=`echo $i | awk -F: '{print $7}'`
let shells[$type]++
done
for i in ${!shells[*]}
do
echo "索引是:$i ,值为:${shells[$i]}"
done
IFS=$OLD_IFS   #再把老的分隔符还原回来

2. 函数               

2.1 概述

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

重点传参 $1,$2

           局部变量 local

           返回值 return 既 $?

2.2 案列1:定义数组和应用数组

需求:通过shell脚本,编写系统工具箱

编写循环脚本,功能菜单

df -hT

free -m

uptime

netstat -anpt

脚本:

#!bin/bash
#打印出工具箱菜单
caidan () {                       #函数名为 caidan
cat <<-EOF                           
------系统工具箱------
------1.查看磁盘信息------
------2.查看内存信息------
------3.查看CPU信息------
------4.退出工具箱-------
EOF
}
while :
do
caidan
read -p "请输入您的选择: "  num
case $num  in
1)
echo "======磁盘信息======"
df -hT
echo "======磁盘信息======"
;;
2)
echo "======查看内存======"
free -m
echo "======查看内存======"
;;
3)
echo "======查看CPU======"
uptime
echo "======查看CPU======"
;;
4)
exit
;;
esac
done

2.3 案列2:利用传参把数据应用到函数里

vim 333.sh
#!/bin/bash
aaa () {
expr $1 \* 10     #expr表示运算符
}
aaa $1        
aaa $2
aaa $3

执行脚本

[root@localhost ~]# bash 333.sh 10 20 30
100
200
300
[root@localhost ~]# 

注释:

执行脚本后面跟的 $1(10),$2(20),$3(30) 这三个参数先一传给脚本里aaa后面的$1,$2,$3,然后再二传给函数aaa里的$1

2.4 案列3:阶乘概念

阶乘:5!=1*2*3*4*5=120

需求

#!/bin/ash
#for c语言的写法 ((初值;条件;步长))
fff=1
for ((i=1;i<=5;i++))  #加上i++才会有限循环,不然i一直为1,不会叠加,就会一直循环
do
fff=$[$fff*$i] 
done
echo "5的阶乘结果是:$fff"

注释: 

#fff=$[$fff*$i] 不是一个数组,而是一个数学表达式。在这个表达式中,fff和$i都是变量,$[ ]表示进行数学计算。这个表达式的含义是将fff乘以i的值,并将结果赋给fff。所以,fff的值会不断地被更新。

2.5 案列4:数组传参,函数结果再赋予数组

场景:用户获赠流量包(每人增加5G),结果运算、

1.把用户利用数组传参给函数

2.利用函数运算

3.函数结果再赋予数组

脚本:

#!bin/bash
num=(1 2 3)
array () {
local j    #local指定义变量j仅在函数中有效
for i in $*
do
outarray[j++]=$[$i+5]   #因为i在上面已经被使用了,所有这里使用j++。定义不同的值乘以5,注意调取值得时候要用索引
done    
echo "${outarray[*]}"   #输出新的数组
}
array ${num[*]}  

3. 影响shell程序的内置命令

3.1 概述

  • shift 使位置参数向左移动,默认移动1位,可以使用shift 2
  • exit 退出整个程序
  • break 结束当前循环,或跳出本层循环
  • continue 忽略本次循环剩余的代码,直接进行下一次循环

以下单独介绍下shift,因为其余三个比较容易理解

3.2 shift 案列

使用for循环,执行脚本后脚本输出成功
[root@localhost ~]# vim test1.sh
#!/bin/bash
for i
do
let sum+=$1
shift
done
echo "总和为 $sum"

[root@localhost ~]# bash test1.sh 1 2 3
总和为 6
当使用while死循环时,如果不加shift,脚本就会一直循环下去。
[root@localhost ~]# vim test2.sh
#!/bin/bash
while [ $# -ne 0 ]
do
let sum+=$1             #sum是一个变量名字,表示拿sum去+$1,然后赋值给sum
done
echo "总和为 $sum"
[root@localhost ~]# bash test2.sh 1 2 3
无结果......
当加了shift后,脚本执行完参数后会自动结束,结果输出成功
[root@localhost ~]# vim test2.sh
#!/bin/bash
while [ $# -ne 0 ]
do
let sum+=$1  
shift     
done
echo "总和为 $sum"

[root@localhost ~]# bash test2.sh 1 2 3
总和为 6

注释:这里的shift作用就体现出来了,执行完一次while后就会把第一个参数$1向左移动(相当于删除),$2上的参数会自动补充到$1上,等执行到$4上的参数也就是$1后,数值为空,脚本自然终止


第五章 三剑客

1. 正则表达式

1.1 名词解释

正则表达式是一种字符模式,用于在查找过程中匹配指定的字符。

在大多数程序里,正则表达式都被置于两个正斜杠之间,例如 /|[oO]ve/ 就是由正斜杠界定的正则表达式

1.2 案例

需求:匹配数字的脚本,用户输入创建账号的数量

#!/bin/bash
read -p "请输入需要创建的测试账号名称: " name
read -p "请输入需要创建的测试账号数量: " teee
if [[ $teee =~ ^[0-9]+$ ]]  # +$指以一个数字或多个数字结尾
then
echo "您输入的是数字"
else
echo "您输入的不是数字"
fi
for i in `seq $teee`
do 
useradd $name$i
echo "账号$name$i 创建完成" 
done

1.3 元字符

  • 分类:基本正则表达式元字符

  ^    行首定位符——例:^root

  $    行尾定位符——例:love$

  .     匹配任意单个字符

  *     匹配前导符0到多次——例:ab* 指a后面可以跟0~多个b

  .*    任意多个字符—— 为什么*.不好使,因为*的前面一定要有前导

  [ ]   匹配指定范围内的一个字符——例:grep [liL] 1.txt ,连续的范围:grep [a-z1-9] 1.txt

  [^]  匹配不在指定组内的字符

  \     用来转移元字符 ('' "" \),脱义符

  \<   词首定位符——例: grep "\<love" 1.txt

  \>   词尾定位符——例:love\>

  ()    匹配稍后使用的字符的标签——例:3,9 s/\(.*\)/#\1/ ,# s指替换,/原来/现在/ ,代表3~9行的前面加#号

                                                    ——例:3,9 s/do/da/g ,#s指替换,g指全局

  x\{m\}     字符x重复出现m次——例:grep "x\{m\}" 1.txt

  x\{m,\}    字符x重复出现m次以上——例:grep "x\{m,\}" 1.txt

  x\{m,n\}  字符x重复出现m次~n次之间——例:grep "x\{m,n\}" 1.txt

  • 扩展正则表达式元字符 注:使用时前面要加e ,比如egrep

  +     匹配1~n个前导字符

  ?   匹配0~1个前导字符

  a|b 匹配 a 或 b

  ()    组字符——例:egrep "love(able|rs)" 1.txt

2. grep

①目的

    过滤,查找文档中的内容

②分类

   grep

   egrep

   fgrep

③参数

    grep -q      静默

    grep -v      取反 例:ps aux | grep ssh | grep -v grep

    grep -R     可以查目录下面的文件

    grep -o     只找到这个关键字就可以

    grep -B2   前两行

    grep -A2   后两行

    grep -C2  上下两行

    egrep -l    只要文件

    egrep -n   带行号

3. sed

3.1 前言

sed是一种在线的、非交互式的编辑器,它一次处理一行内容(逐行处理文件),处理时,把当前处理的行存储在临时缓冲区中,称为“模式空间”,接着用sed命令处理缓冲区中的内容,处理完成后,把缓冲区的内容送往屏幕。

3.2 格式

sed 选项 命令 文件

sed 选项 -f 脚本 文件

3.3 返回值

都是0,对错不管。

只有当命令存在语法错误时,sed的退出状态才是非0

3.4 sed和正则表达式

使用扩展元字符的方式:sed -r 一定要加-r,后面才可以跟正则符号 (加 -ri 指更改到文件里)

3.5 汇总示例 重点

删除命令:d

sed  -ri  '1,$d'  passwd      #指将passwd文件从第一行到最后一行的内容删除
sed  -ri  '1d'  passwd        #指将passwd文件的第一行内容删除

替换命令:s

sed -ri 's/root/aofa/'  passwd   #指将passwd文件内容里的root替换成aofa
sed -ri 's/^root/aofa/' passwd   #指将passwd文件内容里以root为行首的那一行内容里的root替换成aofa
sed -ri 's/[0-9][0-9]$/&.5/' passwd # &和\1是一个意思,指调用前面的内容。指将以两个数字结尾的行后面加上.5
:% s/(.*)/#\1/g  #指将该文件的所有内容前面加上# 
:% s/(.*)/#&/g   #指将该文件的所有内容前面加上#

:% s/\${\([^}]\+\)}/\1/g

读文件命令:r

sed -ri 'r 3.txt'  passwd    指读取3.txt里面的文件然后追加到passwd文件内容

写文件命令:w(另存为)

sed -ri 'w 111.txt' 1.txt  #指将1.txt里面的内容另存到111.txt里
sed -ri '/root/w 111.txt' passwd  #指将passwd里带root的行另存到111.txt里
sed -ri '1,5w 111.txt' passwd  #指将passws里的1~5行内容另存到111.txt里

追加命令:a(之后)

sed  -ri  'a123'  passwd      指在passwd文件内容里的每行后面加上123
sed  -ri  '2a123'  passwd    指在passwd文件内容里的第二行后面加上12

插入命令:i (之前)

sed  -ri  'i123'  passwd      指在passwd文件内容里的每行前面加上123
sed  -ri  '2i123'  passwd    指在passwd文件内容里的第二行前面加上123

替换整行命令:c

sed -ri '2,3c333333\    (回车换行)
> 444444' passwd     #指将passwd文件内容里的第二行和第三行换成了333333和444444
sed -ri '/^SELINUX=/c SELINUX=disabled' /etc/selinux/config  #指把config文件里以SELINUX开头的那一行,内容换成指定的SELINUX=disabled

获取下一个行命令:n

sed -ri '/root/{n;d}' passwd   #指在passwd文件内容里将带有root一行的下一行删除
sed -ri '/root/{n;s/bin/ding/g}' passwd  #指在passwd文件内容里将带有root一行的下一行里的bin全部都替换成ding, g指全局替换

反向选择:!

sed -ri '2,$d' passwd   #指在passwd文件内容里将第2行到最后一行删除
sed -ri '2,$!d' passwd  #指在passwd文件内容里将第2行到最后一行之外的行删除

多重编辑:e

sed -r -e '1,3d' -e '4s/adm/admin/g' passwd   #指在passwd文件内容里将1~3行删除,然后将第四行的adm全部换成admin

  4. awk

4.1 前言

  •     awk是一种编程语言,用于在linux/unix下对文本和数据进行处理,数据可以来自标准输入、一个或多个文件,或其它命令的输出。它支持用户自定义函数和动态正则表达式等先进功能。
  •     awk的处理文本和数据的方式是这样的:它逐行扫描文件,从第一行到最后一行,寻找匹配的特定模式的行,并在这些行上进行你想要的操作。如果没有指定处理动作,则把匹配的行显示到标准输出(屏幕)。

4.2 工作原理

1)awk使用一行作为输入,并将这一行赋给内部变量¥0,每一行也可称为一个记录,以换行符结束。

2)然后,行被:(默认为空格或制表符)分解成字段(或域),每个字段存储在已编号的变量中,从$1开始,最多达100个字段。

3)awk输出之后,将从文件中获取另一行,并将其存储在$0中,覆盖原来的内容,然后将新的字符串分隔成字段并进行处理。该过程将持续到所有行处理完毕。

4.3 语法

awk [选项] 'command' 1.txt  #command指命令
===command (时空):
BEGIN{} {} END{}   #BEGIN{}指发生在行处理前
{}                 #指发生在行处理时,读一次执行一次
END{}              #指行处理后
示例:awk 'BEGIN{print 1/2} {print $1} END{print "终于干完了"}' /etc/hosts

4.4 内部变量

FS   #输入字段分隔符(默认空格)
     示例:awk 'BEGIN{FS=":"}{print $1}' passwd
OFS  #输出字段分隔符(默认空格)
     示例:awk 'BEGIN{FS=":";OFS="++++"}{print $1,$2,$3}' passwd
以上要记住,以下了解即可
RS   #输入记录(行)分隔符,默认换行符
     示例:awk 'BEGIN{RS=" "} {print $0 }' 1.txt   #RS=" "指的是把原来的换行符\n换成了空格
ORS  #输出记录(行)分隔符,默认换行符    
     示例:awk 'BEGIN{ORS="+++"} {print $0 }' 1.txt
FNR  #多文件独立编号
     示例:awk '{print FNR,$0}' 1.txt 2.txt
NR   #多文件汇总编号     
     示例:awk '{print NR,$0}' 1.txt 2.txt         
NF   #字段总数
     示例:awk -F: '{print NF,$0}' passwd

4.5 格式化输出

date | awk '{print "当前的年份是:" $1,"当前的月份是:" $2}'
awk -F: '{print "账户名:" $1 "\t用户的ID是:" $3}' passwd  #\t和,是一个意思,\t的间隔比,长一点

4.6 模式(正则表达)和动作

字符串比较

awk '/^root/' /etc/passwd       
awk '$0~/^root/' /etc/passwd    #匹配以root为开头的行
awk '$0!~/^root/' /etc/passwd   #匹配不是以root为开头的行
awk -F: '$1~/^root/' /etc/passwd  #匹配第一列是以root开头的行

数字比较

awk -F: '$3 > 10' passwd     #匹配第三列大于10的行
awk -F: '$3 == 123' passwd   #匹配第三列等于123的行
awk -F: '$3 * 10 > 100' passwd  #匹配第三列的数字乘以10后并且大于100的行

多条件

语法:
&& 逻辑与 a&&b    #和
|| 逻辑或 a||b    #或
!  逻辑非 !a     #非
示例:
awk -F: '$1~/root/ && $3 == 0' passwd
awk -F: '$1~/root/ || $3 == 0' passwd
awk -F: '$1~/root/ ! $3 == 0' passwd

范围查找

awk -F: '/7/,/12/' passwd   #查找7到12之家的行内容

4.7 awk脚本编程

4.7.1 变量

awk调用变量:自定义内部变量 -v

内部变量-v示例:

awk -F: -v bianliang=user71 '$1 == bianliang' /etc/passwd

外部变量调用" ' "示例:

heihei=shutdown
echo $heihei
awk -F: '$1 ~ "'"$heihei"'" '  passwd  #调用外部变量时要加单引,单眼的外面还要加双引

4.7.2 条件&判断

if 语句格式

if (条件测试) {执行语句}

需求:如果$3是0,就说他是管理员
awk -F: '{if($3==0){print $1 "是管理员"}}' /etc/passwd

if ...else语句

if (条件测试) {执行语句}else{条件不成立,执行语句}

需求:如果$3是0,就说他是管理员,否则就说它是普通用户
awk -F: '{if($3==0){print $1 "是管理员"}else{print $1 "是普通用户"}}' /etc/passwd

需求:统计管理员和普通用户的数量
awk -F: '{if($3==0){i++}else{a++}} END{print i;print a}' /etc/passwd

if...else if...else语句

if(){}   else if(){}   else if (){}    elese

4.7.3 循环

while

需求:循环打印10个数字
awk 'BEGIN{while(i<=10){print i;i++}}' 

需求:第一行内容打印十次
awk -F: '{while(i<=9) {print $0;i++}}' passwd

for

需求:循环打印5个数字
awk '{for(i=1;i<=5;i++){print i}}' 

需求:将每行内容打印10次
awk -F: '{for(i=1;i<=10;i++){print $0}}' passwd

需求:将每一行的第一列打印10次
awk -F: '{for(i=1;i<=10;i++){print $1}}' passwd

4.7.4 数组

定义数组

需求:将用户定义为数组的值,打印第一个值
awk -F: '{username[++i]=$1} END {print username[1] }' passwd

需求:将用户定义为数组的值,打印所有的值
awk -F: '{username[++i]=$1} END{for (i in username){print i,username[i] }}' passwd | sort -n  #sort指排序,-n指以数字排序

4.7.5 awk编程案例

需求:统计/etc/passws中各种类型shell的数量
awk -F: '{shells[$NF]++} END{for (i in shells){print i,shells[i]}}' /etc/passwd  #$NF指总列数,这里也可以换成$7
  • 17
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值