本文目录
第一章 变量
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指定的文件或目录存在并且可执行时返回真
-p filename 当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