linux—shell 脚本编程
1、基础概念
1.1 程序组成
程序:算法 + 数据结构
数据:是程序的核心
算法:处理数据的方式
数据结构:数据在计算机中的类型和组织方式
1.2 编程分格
过程式:以指令为中心,数据服务于指令
对象式:以数据为中心,指令服务数据
1.3 编程语言
计算机:运行二进制指令
编程语言:人与计算机之间交互的语言,分低级语言和高级语言
低级:汇编
高级:
编译:高级语言 → 编译器 → 目标代码(java,C),管理员要编译
解释:高级语言 → 解释器 → 机器代码(shell,perl,python),底层自动翻译
2、shell 程序
shell 编程:过程式、解释执行
shell 编程语言的基本结构:
各种系统命令的结合
数据存储:变量、数组
表达式:a+b(例)
语句:if(例)
2.1 shell 脚本基本格式
(1)首行 shebang 机制
#!/bin/bash #!/usr/bin/python #!/usr/bin/perl
(2)脚本规范
第一行一般为调用使用的语言(shebang 机制)
紧接着是注释说明,一般包含如下信息:
程序名,避免更改文件名而无法找到正确的文件;
版本号;
更改时间;
作者相关信息;
该程序的作用,及注意事项;
最后是各版本的更新简要说明。
#例: #!/bin/bash # #********************************************* #Author: #Blog: #Time:2020-05-24 11:53:58 #Name:hello.sh #Version:V1.0 #Description:This is a test script. #*********************************************
2.2 shell 脚本的用途
自动化常用命令;
执行系统管理和故障排除;
创建简单的应用程序;
处理文本或文件等。
2.3 创建、执行脚本的过程
(1)使用文本编辑器来创建文本文件
首行 shebang 机制;
注释说明信息。
(2)运行脚本
方法一:赋予脚本执行权限,指定绝对或相对路径执行脚本;
./hello.sh #相对路径 /data/shell/hello.sh #绝对路径
方法二:赋予脚本执行权限,加入 PATH 变量路径,直接运行脚本;
chmod +x /data/shell/hello.sh vim /etc/profile export PATH=/data/shell/:$PATH hello.sh #执行
方法三:直接运行解释器,将脚本作为解释器程序的参数运行。
bash test.sh cat test.sh | bash
方法四:搭建脚本服务器,存放脚本文件,需要使用时,远程拉取运行解释器执行。
curl http://10.0.0.3/hello.sh | bash
2.4 脚本测试
bash -n test.sh # 检查语法是否错误 bash -x test.sh # 跟踪调试执行
2.5 变量
字符串在内存中存存储以二进制进行存储、寻找,而我们使用变量名来访问该字符串。也可以重新定义字符串,重新画一块地址空间存放新字符串,指针指向新字符串的内存空间,原来的内存空间也在但是可以回收了。
(1)变量的作用
1> 数据存储格式;
2> 参与的运算;
3> 表示的数据范围。
(2)变量的类型
字符:shell 不分数据类型,都认为是字符串
数值:整型、浮点型
强类型:变量不经过强制转换,它永远是这个数据类型,不允许隐式的类型转换。一般定义变量时必须指定类型、参与运算必须符合类型要求,调用未声明变量会产生错误。
如 java,c
弱类型:语言的运行时会隐式做数据类型转换。无须指定类型,默认均为字符型,参与运算会自动进行隐式类型转换,变量无须事先定义可直接调用。
如:bash 不支持浮点数,php
(3)变量命名法则
1> 不能使用程序中的保留字:例如 if,for
2> 只能使用数字、字母及下划线,且不能以数字开头
3> 见名知意
4> 统一命名规则:驼峰命名法
[root@centos7 ~]# NAME="zhangsan" # 定义变量 [root@centos7 ~]# echo $NAME # 显示变量 zhangsan [root@centos7 ~]# HOSTNAME=`hostname` [root@centos7 ~]# echo $HOSTNAME centos7.localdomain [root@centos7 ~]# set # 显示所有变量 [root@centos7 ~]# unset HOSTNAME # 删除变量
(4)set 定制 shell 环境
[root@centos7 ~]# echo $- himBH h:开启hash i:交互式 m:监控模式(进程前后端切换) B:{} H:历史命令 [root@centos7 ~]# set +h # 禁用hash [root@centos7 ~]# hash -bash: hash: 哈希已禁用 [root@centos7 ~]# set -h # 启用hash # 脚本执行建议设置: set -uex u:在扩展一个没有设置的变量时,显示错误信息 e:脚本中当遇到错误,后续将不执行 x:相当于bash -x
(5)bash 中变量的种类
局部变量:生效范围为当前 shell 进程
[root@centos7 ~]# NAME="zhangyy" [root@centos7 ~]# echo $NAME zhangyy
环境(全局变量):生效范围为当前 shell 进程及其子进程
#定义环境变量:export 或 declare -x [root@centos7 ~]# export NAME="zhangsan" [root@centos7 ~]# echo $NAME zhangsan [root@centos7 ~]# echo $BASHPID 8196 [root@centos7 ~]# bash # 开启子进程 [root@centos7 ~]# echo $NAME zhangsan [root@centos7 ~]# echo $BASHPID 28738 [root@centos7 ~]# exit exit #显示所有的环境变量:env
本地变量:生效范围为当前 shell 进程中某代码片断,通常指函数
位置变量:在脚本中调用通过命令行传递给脚本的参数(脚本后跟参数实现不同功能)
$1,$2,…:对应第 1、第2...参数 $0 :命令本身(针对同一程序创建不同软链接,$0 值不同则实现不同功能) $* :传递给脚本的所有参数,全部参数合为一个字符串 $@ :传递给脚本的所有参数,每个参数为独立字符串 $# :传递给脚本的参数个数 $@ $* : 只在被双引号包起来才会有差异(脚本调用的时候) set -- : 清空所有位置变量
特殊变量:$?,$$(当前进程的进程编号),$PPID(父进程的进程 ID)
只读变量:只能声明,但不能修改和删除
#声明只读变量: readonly PI=3.1415926 #显示所有只读变量: readonly 或 declare -r
2.6 bash 如何开展命令行
(1)把命令行分成单个命令;
(2)展开别名;
(3)展开大括号 {} 声明,例:echo {1..3}
(4)展开波浪符 ~ 声明,例:家目录
(5)命令替换 $() 或 ``,例:`hostname`
(6)再次把命令行分成命令;
(7)展开文件通配符;
(8)准备 I/O 重导向;
(9)运行命令。
2.7 第一个脚本
第一个脚本实现:拷贝脚本,拷贝本机文件至远程主机
[root@centos7 ~]# cat scp.sh #!/bin/bash # #********************************************* #Author:zhang #Blog: #Time:2020-05-26 18:01:31 #Name:scp.sh #Version:V1.0 #Description:This is a test script. #********************************************* echo "Start copy..." scp $* root@192.168.100.111:/data/ # $*: 指传递给脚本的参数 echo "End of copy."
[root@centos7 ~]# [root@centos7 ~]# bash scp.sh anaconda-ks.cfg Start copy... root@192.168.100.111's password: anaconda-ks.cfg 100% 1530 783.2KB/s 00:00 End of copy.
2.8 运算
(1)算数运算
+,-,*,/,%,**
实现算数运算:
方法一:let var=算术表达式
[root@centos7 ~]# let sum=1+2+3+4 [root@centos7 ~]# echo $sum 10
方法二:var=$[算术表达式]
[root@centos7 ~]# z=$[2*3] [root@centos7 ~]# echo $z 6
方法三:var=$((算术表达式))
[root@centos7 ~]# a=6;b=7;c=$((a+b)) [root@centos7 ~]# echo $c 13
方法四:var=$(expr arg1 arg2 arg3…)
[root@centos7 ~]# n=$(expr 6 * 7) expr: 语法错误 [root@centos7 ~]# n=$(expr 6 \* 7) # *号得转义 [root@centos7 ~]# echo $n 42
方法五:echo ‘算术表达式’ | bc
[root@centos7 ~]# echo {1..100} | tr ' ' '+' | bc 5050
(2)逻辑运算
0: false,1: true
与: &,或: | 0&0=0 0|0=0 0&1=0 0|1=1 1&0=0 1|0=1 1&1=1 1|1=1
非: !
异或: ^ 0^1=1
0^0=0
1^0=1
1^1=0
短路与: &&,短路或: || 0&&0=0 0||0=0 0&&1=0 0||1=1 1&&0=0 1||0=1 1&&1=1 1||1=1
短路与应用:&&
cmd1 && cmd2 # 如果cmd1为真,执行cmd2;如果cmd1为假,不执行cmd2
短路或应用:||
cmd1 || cmd2 # 如果cmd1为假,执行cmd2;如果cmd1为真,不执行cmd2
#异或^实例: [root@centos7 ~]# a=2 [root@centos7 ~]# b=3 [root@centos7 ~]# let c=a^b [root@centos7 ~]#echo $c 1 # 2=0010 # 3=0011 # ^——————— # c=0001 [root@centos7 ~]# echo $[a^c] 3 #异或实现ab值互换: [root@centos7 ~]#a=4 [root@centos7 ~]#b=5 [root@centos7 ~]#a=$[a^b];b=$[a^b];a=$[a^b];echo $a $b 5 4
#逻辑与&&实例: [root@centos7 ~]# ping -c1 -W2 www.baidu.com &> /dev/null && echo "www.baidu.com network reachable"
www.baidu.com network reachable #逻辑或||实例: [root@centos7 ~]# id yang uid=1000(yang) gid=1000(yang) 组=1000(yang) [root@centos7 ~]# id mimi id: mimi: no such user [root@centos7 ~]# id yang &> /dev/null || useradd yang # 如果yang用户不存在则创建用户 [root@centos7 ~]# id mimi &> /dev/null || useradd mimi # 如果mimi用户不存在则创建用户 [root@centos7 ~]# id mimi uid=1005(mimi) gid=1005(mimi) 组=1005(mimi)
2.9 条件测试
判断某需求是否满足,需要有测试机制来实现,专用的测试表达式需要由测试命令辅助完成测试过程。
评估布尔声明,以便用于在条件性执行中:
若真,则返回 0
若假,则返回 1
测试命令:
test EXPRESSION
[ EXPRESSION ]
[[ EXPRESSION ]]
注意:EXPRESSION 前后必须有空白字
(1)数字测试
-v VAR: 判断变量 VAR 是否设置 -eq : 等于 -ne : 不等 -lt : 小于 -le : 小于等于 -gt : 大于 -ge : 大于等于 #注: 比较的时候,如果变量没有值,会报错所以在[ ]内要加上"" :专门返回正确什么也不干 false 专门返回错误结果什么也不干
(2)字符串测试
= : 是否等于 > : 是否大于(ascii 码) < : 是否小于 != : 是否不等于 == : 右边支持通配符 =~ : 右侧支持扩展正则 -z "STRING" : 字符串是否为空,空为真,不空为假 -n "STRING" : 字符串是否为不空,不空为真,空为假
[root@centos7 ~]# n=34 [root@centos7 ~]# [[ "$n" =~ ^[[:digit:]]+$ ]] && echo "digit" || echo "no digit" digit [root@centos7 ~]# ll scp.sh -rwxr-xr-x 1 root root 339 5月 26 18:06 scp.sh [root@centos7 ~]# filename=scp.sh [root@centos7 ~]# [[ $filename =~ .+\.sh$ ]] && echo sh || echo no sh sh [root@centos7 ~]# filename=anaconda-ks.cfg [root@centos7 ~]# [[ $filename =~ .+\.sh$ ]] && echo sh || echo no sh no sh
[root@centos7 ~]# IP=1.2.3.4; [[ "$IP" =~ ^([0-9]{1,3}.){3}[0-9]{1,3}$ ]]; echo $?
0
[root@centos7 ~]# IP=1.2.3.4.5; [[ "$IP" =~ ^([0-9]{1,3}.){3}[0-9]{1,3}$ ]]; echo $?
1
# 判断空字符串
[ -z $var ]
[ ! -n $var ]
[ "$var"="" ]
[ x"var"="x" ]
(3)文件测试
#文件存在性测试: -a FILE:同-e -e FILE:文件存在性测试,存在为真,否则为假 #文件存在性及类别测试: -b FILE:是否存在且为块设备文件 -c FILE:是否存在且为字符设备文件 -d FILE:判断是否为目录文件 -f FILE:是否存在且为普通文件 -h FILE 或-L FILE:是否存在且为符号链接文件 -p FILE:是否存在且为命令管道文件 -S FILE:是否存在且为套接字文件 #文件权限测试: -r FILE:是否存在且可读 -w FILE:是否存在且可写 -x FILE:是否存在且可执行 -u FILE:是否存在且拥有 suid 权限 -g FILE:是否存在且拥有 sgid 权限 -k FILE:是否存在且拥有 sticky 权限 #文件属性测试: 文件大小测试: -s FILE:是否存在且非空 文件是否打开: -t fd:fd 文件描述符是否在某终端已经打开 -N FILE:文件自从上一次被读取之后是否别修改过 -O FILE:当前有效用户是否为文件属主 -G FILE:当前有效用户是否为文件属组 #文件双目测试: FILE1 -ef FILE2:FILE1 是否是 FILE2 的硬链接 FILE1 -nt FILE2:FILE1 是否新于 FILE2(mtime) FILE1 -ot FILE2:FILE1 是否旧于 FILE2 #help test 查看更多(test 是内置命令)
[root@centos7 ~]# [ -d /etc ]; echo $? 0 [root@centos7 ~]# [ -d /etc/issue ]; echo $? 1 [root@centos7 ~]# ll /etc/shadow ---------- 1 root root 806 5月 25 15:55 /etc/shadow [root@centos7 ~]# [ -x /etc/shadow ]; echo $? 1
(4)组合测试
第一种方式: COMMAND1 && COMMAND2 并且 COMMAND1 || COMMAND2 或者 第二种方式: EXPRESSION1 -a EXPRESSION2 并且 EXPRESSION1 -o EXPRESSION2 或者 ! EXPRESSION
[root@centos7 ~]# ll anaconda-ks.cfg -rw------- 1 root root 1530 4月 16 14:36 anaconda-ks.cfg [root@centos7 ~]# FILE=anaconda-ks.cfg [root@centos7 ~]# [ -f $FILE -a -x $FILE ]; echo $? 1 [root@centos7 ~]# [ -f $FILE -o -x $FILE ]; echo $? 0 [root@centos7 ~]# [ ! -x $FILE ]; echo $? 0
[root@centos7 ~]# [ -f $FILE ] && [[ "$FILE" =~ .*\.sh$ ]] && chmod +x $FILE
13.5 ()小括号开启子进程
[root@centos7 ~]# name=zhang;(echo $name;name=yang;echo $name);echo $name zhang yang zhang
14、使用 read 命令来接受输入
read [-option] [name] -p:指定要显示的提示 -s:静默输入,一般用于密码 -n N:指定输入的字符串长度 N -d:’字符’ 输入结束符 -t N:TIMEOUT 为 N 秒 注:read 从标准输入中读取值,给每个单词非配一个变量,所有剩余单词都被分配给最后一个变量
[root@centos7 ~]# read -p "please enter your name:" name please enter your name:yang [root@centos7 ~]# echo $name yang [root@centos7 ~]# read var1 var2 var3 #建议一次定义一个值 1 2 3 [root@centos7 ~]# echo $var1 $var2 $var3 1 2 3
[root@centos7 ~]# echo zhangyy | read NAME #开启子进程
[root@centos7 ~]# echo $NAME
[root@centos7 ~]# echo zhangyy | { read NAME; echo $NAME; }
zhangyy
15、bash shell 配置文件
/etc/profile #全局配置
~/.bashrc #个人配置
15.1 交互式登录:
(1)直接通过终端输入账号密码登录
(2)使用 "su - UserName" 切换的用户
配置文件执行顺序: /etc/profile --> /etc/profile.d/*.sh --> ~/.bash_profile --> ~/.bashrc --> /etc/bashrc
15.2 非交互式登录:
(1)su UserName
(2)图形界面下打开的终端
(3)执行脚本
(4)任何其它的 bash 实例
配置文件执行顺序: /etc/profi1e.d/*.sh --> /etc/bashrc --> ~/.bashrc
15.3 按功能划分分类:
(1)profile 类:
profile 类为交互式登录的 shell 提供配置
全局:/etc/profile, /etc/profile.d/*.sh
个人:~/.bash_profile
功用:
1.用于定义环境变星
2.运行命令或脚本
(2)bashrc 类:
bashrc 类为非交互式和交互式登录的 shell 提供配置
全局:/etc/bashrc
个人:~/.bashrc
功用:
1.定义命令别名和函数
2.定义本地变量
15.4 编辑配置文件生效:
修改profile和bashrc文件后需生效两种方法:
1.重新启动shell进程
2.shurce|. 配置文件
15.5 bash退出任务:
保存在 .bash_logout 文件中
功能:自动备份、清理临时文件
16、流程控制
16.1 单分支:
#语法:
if 判断条件;then 条件为真的分支代码 fi
16.2 双分支:
#语法:
if 判断条件;then 条件为真的分支代码 else 条件为假的分支代码 fi
16.3 多分支:
#语法:
if 判断条件1;then 条件1为真的分支代码 elif 判断条件2;then 条件2为真的分支代码 elif 判断条件3;then 条件3为真的分支代码 else 以上条件为假的分支代码 fi
#实例:BMI指数 [root@centos7 ~]# cat if_bmi.sh #!/bin/bash # #********************************************* #Author:zhang #Blog: #Time:2020-05-26 13:53:00 #Name:if_bmi.sh #Version:V1.0 #Description:This is a test script. #********************************************* read -p "Please input your height:" HEIGHT if [[ ! $HEIGHT =~ ^[0-2].?[0-9]{,2}$ ]];then echo "输入错误的身高";exit 1;fi read -p "Please input your weight:" WEIGHT if [[ ! $WEIGHT =~ ^[0-9]{1,3}$ ]];then echo "输入错误的体重";exit 1;fi BMI=`echo $WEIGHT/$HEIGHT^2|bc` if [ $BMI -le 18 ];then echo "太瘦了,多吃点" elif [ $BMI -lt 24 ];then echo "身材很棒" else echo "肥胖" fi [root@centos7 ~]# bash if_bmi.sh Please input your height:1.8 Please input your weight:60 太瘦了,多吃点
16.4 条件判断 case 语句,适合通配符的匹配
#语法:
case 变量引用 in PAT1) 分支1 ;; PAT2) 分支2 ;; ... *) 默认分支 ;; esac*
#实例: [root@centos7 ~]# cat case_yesno.sh #!/bin/bash # #********************************************* #Author:zhang #Blog: #Time:2020-05-26 14:18:33 #Name:case_yesno.sh #Version:V1.0 #Description:This is a test script. #********************************************* read -p "Whether to install(yes/no)?" INSTALL INSTALL=`echo $INSTALL | tr 'A-Z' 'a-z'` case $INSTALL in y|yes) echo "install." ;; n|no) echo "not install!" ;; *) echo "input error!please input yes or no!" esac [root@centos7 ~]# bash case_yesno.sh Whether to install(yes/no)?y install. [root@centos7 ~]# bash case_yesno.sh Whether to install(yes/no)?no not install!
17、循环控制
17.1 for 循环
#语法1:
for 变量名 in 列表;do 循环体 done
#实例: [root@centos7 ~]# for i in {1..10};do echo $i;done 1 2 3 4 5 6 7 8 9 10 [root@centos7 ~]# for USER in `getent passwd | cut -d: -f1`;do echo username:$USER;done username:root username:bin username:daemon username:adm username:lp ......
#脚本实例:批量创建用户 [root@centos7 ~]# cat for_user.sh #!/bin/bash # #********************************************* #Author:zhang #Blog: #Time:2020-05-26 14:45:56 #Name:for_user.sh #Version:V1.0 #Description:This is a test script. #********************************************* USER=" xiaozhang xiaoyang xiaomu xiaoli " for NAME in $USER;do useradd $NAME echo 111111 | passwd --stdin $NAME &> /dev/null echo "$NAME created successfully." done
#脚本实例:99乘法表 [root@centos7 ~]# cat for_99.sh #!/bin/bash # #********************************************* #Author:zhang #Blog: #Time:2020-05-26 16:25:20 #Name:for_99.sh #Version:V1.0 #Description:This is a test script. #********************************************* for i in {1..9};do for j in `seq $i`;do x=$[i*j] echo -e "${j}X${i}=${x}\t\c" done echo done [root@centos7 ~]# bash for_99.sh 1X1=1 1X2=2 2X2=4 1X3=3 2X3=6 3X3=9 1X4=4 2X4=8 3X4=12 4X4=16 1X5=5 2X5=10 3X5=15 4X5=20 5X5=25 1X6=6 2X6=12 3X6=18 4X6=24 5X6=30 6X6=36 1X7=7 2X7=14 3X7=21 4X7=28 5X7=35 6X7=42 7X7=49 1X8=8 2X8=16 3X8=24 4X8=32 5X8=40 6X8=48 7X8=56 8X8=64 1X9=9 2X9=18 3X9=27 4X9=36 5X9=45 6X9=54 7X9=63 8X9=72 9X9=81
#实例实现:1+2+···+100 [root@centos7 ~]# cat for_sum.sh #!/bin/bashsum=0 for i in {1..100};do let sum+=i done echo $sum
#语法2: for (( exp1; exp2; exp3 )); do COMMANDS; done Frithmetic for loop. #执行流程: 1.执行exp1 2.执行exp2(条件判断) 3.exp2成功,执行COMMANDS;exp2失败,退出循环 4.执行完COMMANDS,执行exp3 5.exp3执行完返回到exp2继续判断
#实例实现:1+2+···+100
[root@centos7 ~]# cat for_sum2.sh #!/bin/bashfor (( sum=0,i=1;i<=100;i++ ));do let sum+=i done echo $sum
#实例实现:扫描网段可访问主机ip [root@centos7 ~]# cat for_scanip.sh net=172.16.1 for (( i=1;i<=254;i++ ));do ping -c1 -W1 $net.$i &> /dev/null && { echo $net.$i is up;echo $net.$i >> host.log; } done
17.2 while 循环
格式: while CONDITION;do 循环体 done
#说明: #CONDITION:循环控制条件
进入条件:CONDITION为true 退出条件:CONDITION为false
#实例实现,磁盘空间报警邮件发送(每一分钟检查一次) [root@centos7 ~]# yum install mailx -y [root@centos7 ~]# cat .mailrc set from=1611748256@qq.com # 发件人 set smtp=smtp.qq.com set smtp-auth-user=1611748256@qq.com set smtp-auth-password=dcrxotejwmiheexxxx # 授权码 set smtp-auth=login set ssl-verify=ignore [root@centos7 ~]# cat while_diskcheck.sh #!/bin/bash WARNING=80 while :;do USE=`df|sed -rn '/^\/dev\/sd/s#.* ([0-9]+)%.*#\1#p'|sort -nr|head -n1` if [ $USE -gt $WARNING ];then echo Disk will be full from `hostname -I`| mail -s "disk warning" zyyyxdz@163.com fi sleep 60 done
#screen命令或tmux命令,实现退出窗口不中断
#菜单实现 [root@centos7 bin]# cat menu.sh #!/bin/bash COLOR="\033[1;31m" END="\033[0m" while true;do cat <<EOF 1) 鲍鱼 2) 龙虾 3) 燕窝 4) 帝王蟹 5) 退出 EOF read -p "请点菜:" MENU case $MENU in 1|2) echo -e $COLOR'菜价:$10'$END let sum+=10 ;; 3) echo -e $COLOR'菜价:$20'$END let sum+=20 ;; 4) echo -e $COLOR'菜价:$30'$END let sum+=30 ;; 5) echo -e ${COLOR}"你点餐的总价格是: ${sum}"$END break ;; *) echo -e $COLOR"点错了,没这道菜"${END} ;; esac echo -e $COLOR"你点餐的总价格是: $sum"$END done
while 循环的特殊用法,遍历文件或文本的每一行
while read line;do 循环体 done < /PATH/FROM/SOMEFILE 说明:依次读取/PATH/FROM/SOMEFILE文件中的每一行,且将行赋值给变量line
#实例:猜数字游戏 [root@centos7 ~]# cat guess.sh #!/bin/bash NUM=$[RANDOM%10] while read -p "请输入0~9之间的数字: " INPUT;do if [ $INPUT -eq $NUM ];then echo "恭喜你猜对了!" break elif [ $INPUT -gt $NUM ];then echo "数字太大了,请重新猜" else echo "数字太小了,请重新猜" fi done
[root@centos7 ~]# cat while_read_diskcheck.sh #!/bin/bash WARNING=80 MAIL=zyyyxdz@163.com df|sed -nr "/^\/dev\/sd/s#^([^ ]+) .* ([0-9]+)%.*#\1 \2#p"|while read DEV USE;do if [ $USE -gt $WARNING ];then echo "$DEV will be full,use:$USE"|mail -s "DISK WARNING" $MAIL fi done
#拒绝黑客攻击 [root@centos7 ~]# cat while_read_check_dos.sh #!/bin/bash lastb|sed -nr '/ssh:/s#.* ([0-9.]{1,4}{3}[0-9]{1,3}) .*#\1#p'|sort|uniq -c|while read count ip;do if [ $count -gt 3 ];then iptables -A INPUT -s $ip -j REJECT fi done
#逐行处理 [root@centos7 ~]# cat while_read_passwd.sh #!/bin/bash while read line;do if [[ "$line" =~ /sbin/nologin$ ]];then echo $line|cut -d: -f1,3 fi done < /etc/passwd
17.3 until 循环
until CONDITION;do 循环体 done
#说明:循环控制条件与while循环相反 进入条件:CONDITION为false 退出条件:CONDITION为true
17.4 continue 和 break 跳出循环
continue [N]:跳出第N层循环的当前循环,最内层为第1层
break [N]:提前结束第N层整个循环,最内层为第1层
[root@centos7 ~]# cat continue_for.sh #!/bin/bashfor(( i=0;i<=10;i++ ));do [ $i -eq 5 ] && continue echo $i done
[root@centos7 ~]# bash continue_for.sh
0
1
2
3
4
6
7
8
9
10
[root@centos7 ~]# cat continue_while.sh #!/bin/bash i=0 while [ $i -le 10 ];do let i++ if [ $i -eq 5 ];then continue fi echo i=$i done [root@centos7 ~]# bash continue_while.sh i=1 i=2 i=3 i=4 i=6 i=7 i=8 i=9 i=10 i=11
17.5 shift 循环控制
[root@centos7 ~]# cat shift_batch_user.sh #!/bin/bashif [ $# -eq 0 ];then echo "Usage: `basename $0` user1 user2 ..." exit fi while [ "$1" ];do if id $1 &> /dev/null;then echo $1 is exist else useradd $1 echo "$1 is created." fi shift done echo "All user is created."
17.6 select 循环与菜单
select variable in list;do 循环体命令 done #说明: select循环主要用于创建菜单,按数字顺序排列的菜单项显示在标准错误上,并显示PS3提示符,等待用户输入 用户输入菜单列表中的某个数字,执行相应的命令 用户输入被保存在内置变量REPLY中 select是个无限循环,因此要记住用break命令退出循环,或用exit命令终止脚本。也可以按ctrl+c退出循环 select经常和case联合使用 与for循环类似,可以省略in list,此时使用位置参量
[root@centos7 ~]# cat select_menu.sh #!/bin/bash PS3="请点菜(1~6): " select MENU in 鲍鱼 龙虾 燕窝 帝王蟹 退出;do case $REPLY in 1|4) echo '价格:$10' ;; 2) echo '价格:$20' ;; 3) echo '价格:$30' ;; 5) break ;; *) echo '请选择正确的菜' ;; esac done [root@centos7 ~]# bash select_menu.sh 1) 鲍鱼 2) 龙虾 3) 燕窝 4) 帝王蟹 5) 退出 请点菜(1~6): 1 价格:$10 请点菜(1~6): 4 价格:$10 请点菜(1~6): 5
#系统初始化脚本 [root@centos7 ~]# cat reset.sh #!/bin/bash PS3="Please select a configuration item (1-5): "
list="
禁用SELinux
关闭防火墙
修改提示符
修改网卡名称
退出
"
select menu in $list;do case $REPLY in 1) sed -i.bak 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config setenforce 0 echo -e "\e[1;31m重新启动后才可生效\e[0m" ;; 2) systemctl stop firewalld ;; 3) echo "PS1='\[\e[1;33m\][\u@\h \W]\\$\[\e[0m\] '" > /etc/profile.d/reset.sh echo -e "\e[1;31m提示符已修改,请重新登录\e[0m" ;; 4) sed -i.bak '/GRUB_CMDLINE_LINUX=/s#"$# net.ifnames=0"#' /etc/default/grub grub2-mkconfig -o /boot/grub2/grub.cfg echo -e "\e[1;31m重新启动方可生效\e[0m" ;; 5) break ;; *) echo -e "\e[1;31m请输入正确的数字\e[0m" esac done
18、函数
函数 function 是由若干条 shell 命令组成的语句块,实现代码重用和模块化编程。
它与 shell 程序形式上是相似的,不同的是它不是一个单独的进程,不能独立运行,而是 shell 程序的一部分
函数和 shel 程序比较相似,区别在于:shell 程序在子 shell 中运行,而 shell 函数在当前 shell 中运行。因此在当前 shell 中,函数可对 shell 中变量进行修改
18.1 管理函数
函数由两部分组成:函数名和函数体
帮助参看:help function
18.1.1 定义函数
#语法一: func_name () { ...函数体... } #语法二: function func_name { ...函数体... } #语法三: function func_name () { ...函数体... }
18.1.2 查看函数
#查看当前已定义的函数名 declare -F #查看当前已定义的函数定义 declare -f #查看指定当前已定义的函数名 declare -F func_name #查看当前已定义的函数名定义 declare -f func_name
18.1.3 删除函数
unset func_name
#实例: [root@centos7 ~]# NAME () { hostname; } [root@centos7 ~]# declare -f NAME NAME () { hostname } [root@centos7 ~]# NAME centos7.localdomain [root@centos7 ~]# unset NAME [root@centos7 ~]# declare -F NAME [root@centos7 ~]#
18.2 函数调用
函数的调用方式:
可在交互式环境下定义函数
可将函数放在脚本文件中作为它的一部分
可放在只包含函数的单独文件中
调用:函数只有被调用才会执行,通过给定函数名调用函数,函数名出现的地方,会被自动替换为函数代码
函数的生命周期:被调用时创建,返回时终止
18.2.1 交互式环境调用函数
#实例: [root@centos7 ~]# testfunc () { echo testfunc;echo function is excuted; } [root@centos7 ~]# testfunc testfunc function is excuted
18.2.2 在脚本中定义和使用函数
[root@centos7 ~]# cat system_initial.sh #!/bin/bash disable_selinux () { sed -i.bak 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config setenforce 0 echo -e "\e[1;31mSELinux已禁用\e[0m" } stop_firewalld () { systemctl stop firewalld &> /dev/null } set_ps1 () { echo "PS1='\[\e[1;33m\][\u@\h \W]\\$\[\e[0m\] '" > /etc/profile.d/ps1.sh echo -e "\e[1;31m提示符已修改,请重新登录\e[0m" } set_eth () { sed -i.bak '/GRUB_CMDLINE_LINUX=/s#"$# net.ifnames=0"#' /etc/default/grub grub2-mkconfig -o /boot/grub2/grub.cfg &> /dev/null echo -e "\e[1;31m网卡名已修改,重新启动方可生效\e[0m" } PS3="Please select a configuration item (1-5): " list=" 禁用SELinux 关闭防火墙 修改提示符 修改网卡名称 以上全部修改 退出 " select menu in $list;do case $REPLY in 1) disable_selinux ;; 2) stop_firewalld ;; 3) set_ps1 ;; 4) set_eth ;; 5) disable_selinux stop_firewalld set_ps1 set_eth ;; 6) break ;; *) echo -e "\e[1;31m请输入正确的数字\e[0m" esac done
18.2.3 使用函数文件
#实例:
[root@centos7 ~]# cat functions add () { echo $[$1+$2] } [root@centos7 ~]# cat test.sh #!/bin/bash source functions add 4 5 [root@centos7 ~]# bash test.sh 9
#实例: [root@centos7 ~]# . /etc/rc.d/init.d/functions [root@centos7 ~]# declare -F action action [root@centos7 ~]# action "Successful execution" Successful execution [ 确定 ] [root@centos7 ~]# action "Execution failed" false Execution failed [失败]
[root@centos7 ~]# cat system_initial_action.sh #!/bin/bash . /etc/rc.d/init.d/functions disable_selinux () { sed -i.bak 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config setenforce 0 action "SELinux已禁用" } stop_firewalld () { systemctl stop firewalld action "防火墙已禁用" } set_ps1 () { echo "PS1='\[\e[1;33m\][\u@\h \W]\\$\[\e[0m\] '" > /etc/profile.d/ps1.sh action "提示符已修改,请重新登录" } set_eth () { sed -i.bak '/GRUB_CMDLINE_LINUX=/s#"$# net.ifnames=0"#' /etc/default/grub grub2-mkconfig -o /boot/grub2/grub.cfg &> /dev/null action "网卡名已修改,重新启动方可生效" } PS3="Please select a configuration item (1-5): " list=" 禁用SELinux 关闭防火墙 修改提示符 修改网卡名称 以上全部修改 退出 " select menu in $list;do case $REPLY in 1) disable_selinux ;; 2) stop_firewalld ;; 3) set_ps1 ;; 4) set_eth ;; 5) disable_selinux stop_firewalld set_ps1 set_eth ;; 6) break ;; *) echo -e "\e[1;31m请输入正确的数字\e[0m" esac done
18.3 函数返回值
函数的执行结果返回值:
使用 echo 等命令进行输出
函数体中调用命令的输出结果
函数的退出状态码:
默认取决于函数中执行的最后一条命令的退出状态码
自定义退出状态码,其格式为:
return 从函数中返回,用最后状态命令决定返回值
return 0 无错误返回
return 1-255 有错误返回
18.4 环境函数
类拟于环境变量,也可以定义环境函数,使子进程也可使用父进程定义的函数
定义环境函数:
export -f function_name
declare -xf function_name
查看环境函数:
export -f
declare -xf
18.5 函数参数
函数可以接受参数
传递参数给函数:在函数名后面以空白分隔给定参数列表即可,如:testfunc arg1 arg2...
在函数体中当中,可使用 $1,$2,... 调用这些参数,还可以使用 $@,$*,$# 等特殊变量
18.6 函数变量
变量作用域:
普通变量:只在当前 shell 进程有效,为执行脚本会启动专用子 shell 进程;因此,本地变量的作用范围是当前 shell 脚本程序文件,包括脚本中的函数
环境变量:当前 shell 和子 shell 有效
本地变量:函数的生命周期,函数结束时变星被自动销毁
注意:
如果函数中定义了普通变量,且名称和局部变量相同,则使用本地变量
由于普通变量和局部变量会冲突,建议在函数中只使用本地变量
在函数中定义本地变量的方法:1ocal NAME=VALUE
18.7 函数递归
函数直接或间接调用自身,注意递归层数一定要有退出条件,否则可能会陷入死循环
#实例:递归实现阶乘 [root@centos7 ~]# cat fact.sh #!/bin/bash fact () { if [ $1 -eq 1 ];then echo 1 else echo $[`fact $[$1-1]`*$1] fi } fact $1 [root@centos7 ~]# bash fact.sh 4 24 [root@centos7 ~]# bash fact.sh 3 6
fork 炸弹是一种恶意程序,它的内部是一个不断在 fork 进程的无限循环,实质是一个简单的递归程序。由于程序是递归的,如果没有任何限制,这会导致这个简单的程序迅速耗尽系统里面的所有资源。
#函数实现: :(){ :|:& };: func () { func | func & } func #脚本实现: vim bomb.sh #!/bin/bash ./$0|./$0&
19、其他脚本相关工具
19.1 信号捕捉:trap
trap '触发指令' 信号 进程收到系统发出的指定信号后,将执行自定义指令,而不会执行原操作 trap '' 信号 忽略信号的操作 trap '-' 信号 恢复原信号的操作 trap -p 列出自定义信号操作 trap finish EXIT 当脚本退出时,执行 finish 函数
#实例:
[root@centos7 ~]# trap -l 1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM 16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP 21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ 26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR 31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3 38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8 43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13 48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12 53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7 58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2 63) SIGRTMAX-1 64) SIGRTMAX [root@centos7 ~]# vim trap.sh [root@centos7 ~]# cat trap.sh #!/bin/bash trap 'echo "press: Ctrl+c"' int for((i=0;i<=5;i++));do echo $i sleep 1 done trap '' int for((i=6;i<=10;i++));do echo $i sleep 1 done trap '-' int for((i=11;i<=15;i++));do echo $i sleep 1 done [root@centos7 ~]# bash -n trap.sh [root@centos7 ~]# bash trap.sh 0 1 ^Cpress: Ctrl+c 2 ^Cpress: Ctrl+c 3 4 5 6 ^C7 ^C8 9 10 11 12 ^C [root@centos7 ~]#
19.2 创建临时文件
mktemp 命令用于创建并显示临时文件,可避免冲突
mktemp [OPTION]... [TEMPLATE] 说明: TEMPLATE:filenameXXX,X至少要出现三个 常见选项: -d 创建临时目录 -P DIR或-tmpdir=DIR 指明临时文件所存放目录位置
#实例: [root@centos7 ~]# mktemp /data/testXXX /data/testh2l [root@centos7 ~]# mktemp -d /data/testdirXXX /data/testdiray9 [root@centos7 ~]# ll /data/ 总用量 0 drwx------ 2 root root 6 6月 3 14:49 testdiray9 -rw------- 1 root root 0 6月 3 14:49 testh2l
19.3 安装复制文件:install
#格式: install [OPTION]... [-T] SOURCE DEST install [OPTION]... SOURCE... DIRECTORY install [OPTION]... -t DIRECTORY SOURCE... install [OPTION]... -d DIRECTORY... #常用选项: -g,--group=Group:指定目标文件的属组; -o,--owner=user:指定目标文件的属主; -m,--mode=mode:指定目标文件的权限模式; -S:设置目标文件的后缀; -D:创建指定文件路径中不存在的目录;
#实例: [root@centos7 ~]# install -m 700 -o zhang -g wang anaconda-ks.cfg /data/a.cfg [root@centos7 ~]# ll anaconda-ks.cfg -rw-------. 1 root root 1736 6月 3 10:54 anaconda-ks.cfg [root@centos7 ~]# ll /data/a.cfg -rwx------ 1 zhang wang 1736 6月 3 15:33 /data/a.cfg [root@centos7 ~]# install -m 770 -d /data/installdir [root@centos7 ~]# ll -d /data/installdir drwxrwx--- 2 root root 6 6月 3 15:34 /data/installdir
19.4 交互式转化批处理工具:expect
expect 是由 Don Libes 基于 Tcl ( Tool Command Language ) 语言开发的,主要应用于自动化交互式操作的场景,借助 expect 处理交互的命令,可以将交互过程如:ssh 登录, ftp 登录等写在一个脚本上,使之自动化完成。
尤其适用于需要对多台服务器执行相同操作的环境中,可以大大提高系统管理人员的工作效率。
expect语法: expect [选项] [ -c cmds ] [ [-[f|b] ] cmdfile ] [ args ] 常见选项: -c:从命令行执行 expect 脚本,默认 expect 是交互地执行的 -d:可以输出输出调试信息 expect 中相关命令: spawn 启动新的进程 expect 从进程接收字符串 send 用于向进程发送字符串 interact 允许用户交互 exp_continue 匹配多个字符串在执行动作后加此命令
#单一分支模式语法: expect "hi" {send "You said hi\n"} 匹配到hi后会输出 "You said hi",并换行
#多分支模式语法: expect "hi" { send "You said hi\n" } \ "hehe" { send "Hehe youself\n" } \ "bye" { send "Good bye\n" }
匹配到hi,hello,bye任意字符串时,执行相应输出,等同于下:
expect {
"hi" { send "You said hi\n" }
"hehe" { send "Hehe youself\n" }
"bye" { send "Good bye\n" }
}
#实例:远程复制 [root@centos7 ~]# cat scp_expect #!/usr/bin/expect spawn scp /etc/fstab 192.168.65.129:/data expect { "yes/no" { send "yes\n";exp_continue } "password" { send "111111\n" } } expect eof [root@centos7 ~]# chmod +x scp_expect [root@centos7 ~]# ./scp_expect spawn scp /etc/fstab 192.168.65.129:/data The authenticity of host '192.168.65.129 (192.168.65.129)' can't be established. ECDSA key fingerprint is SHA256:DEqxIaww8cDItGbP865WxN1kCHxztX4z/ZzGpPCdXlE. ECDSA key fingerprint is MD5:20:cb:f5:2c:55:4b:3f:01:10:27:c3:32:01:93:40:12. Are you sure you want to continue connecting (yes/no)? yes Warning: Permanently added '192.168.65.129' (ECDSA) to the list of known hosts. root@192.168.65.129's password: fstab 100% 541 28.0KB/s 00:00 [root@centos7 ~]#
#实例:远程连接 [root@centos7 ~]# cat ssh_expect #!/usr/bin/expect spawn ssh 192.168.65.129 expect { "yes|no" { send "yes\n";exp_continue } "password" { send "111111\n" } } interact [root@centos7 ~]# chmod +x ssh_expect [root@centos7 ~]# ./ssh_expect spawn ssh 192.168.65.129 root@192.168.65.129's password: Last login: Wed Jun 3 11:16:35 2020 from 192.168.65.1 [root@centos7 ~]# exit 登出 Connection to 192.168.65.129 closed. [root@centos7 ~]#
expect 变量:
#实例:远程连接(改进-设置expect变量) [root@centos7 ~]# cat ssh_expect2 #!/usr/bin/expect set ip 192.168.65.129 set user root set password 111111 set timeout 10 spawn ssh $user@$ip expect { "yes|no" { send "yes\n";exp_continue } "password" { send "111111\n" } } interact [root@centos7 ~]# chmod +x ssh_expect2 [root@centos7 ~]# ./ssh_expect2 spawn ssh root@192.168.65.129 root@192.168.65.129's password: Last login: Wed Jun 3 21:41:16 2020 from 192.168.65.128 [root@centos7 ~]# exit 登出 Connection to 192.168.65.129 closed. [root@centos7 ~]#
expect 位置参数:
#实例:远程连接(改进-设置expect位置参数)
[root@centos7 ~]# cat ssh_expect3 #!/usr/bin/expect set ip [lindex $argv 0] set user [lindex $argv 1] set password [lindex $argv 2] set timeout 10 spawn ssh $user@$ip expect { "yes|no" { send "yes\n";exp_continue } "password" { send "111111\n" } } interact [root@centos7 ~]# chmod +x ssh_expect3 [root@centos7 ~]# ./ssh_expect3 192.168.65.129 root 111111 spawn ssh root@192.168.65.129 root@192.168.65.129's password: Last login: Wed Jun 3 21:43:32 2020 from 192.168.65.128 [root@centos7 ~]# exit 登出 Connection to 192.168.65.129 closed. [root@centos7 ~]#
expect 执行多个指令:
#实例:远程连接(改进-通过expect执行多个指令,远程登录、创建用户、退出)
[root@centos7 ~]# cat ssh_expect4 #!/usr/bin/expect set ip [lindex $argv 0] set user [lindex $argv 1] set password [lindex $argv 2] set timeout 10 spawn ssh $user@$ip expect { "yes|no" { send "yes\n";exp_continue } "password" { send "111111\n" } } expect "]#" { send "useradd haha\n" } expect "]#" { send "echo 111111 | passwd --stdin haha\n" } send "exit\n" expect eof [root@centos7 ~]# chmod +x ssh_expect4 [root@centos7 ~]# ./ssh_expect4 192.168.65.129 root 111111 spawn ssh root@192.168.65.129 root@192.168.65.129's password: Last login: Wed Jun 3 21:45:54 2020 from 192.168.65.128 [root@centos7 ~]# useradd haha [root@centos7 ~]# echo 111111 | passwd --stdin haha 更改用户 haha 的密码 。 passwd:所有的身份验证令牌已经成功更新。 [root@centos7 ~]# exit 登出 Connection to 192.168.65.129 closed. [root@centos7 ~]#
shell 脚本调用 expect:
#实例:远程连接(改进-通过脚本调用expect)
[root@centos7 ~]# cat ssh_expect.sh #!/bin/bash ip=$1 user=$2 password=$3 expect <<EOF set timeout 10 spawn ssh $user@$ip expect { "yes|no" { send "yes\n";exp_continue } "password" { send "111111\n" } } expect "]#" { send "useradd hihi\n" } expect "]#" { send "echo 111111 | passwd --stdin hihi\n" } expect "]#" { send "exit\n" } expect eof EOF [root@centos7 ~]# bash ssh_expect.sh 192.168.65.129 root 111111 spawn ssh root@192.168.65.129 root@192.168.65.129's password: Last login: Wed Jun 3 21:52:38 2020 from 192.168.65.128 [root@centos7 ~]# useradd hihi 正在创建信箱文件: 文件已存在 [root@centos7 ~]# echo 111111 | passwd --stdin hihi 更改用户 hihi 的密码 。 passwd:所有的身份验证令牌已经成功更新。 [root@centos7 ~]# exit 登出 Connection to 192.168.65.129 closed. [root@centos7 ~]#
20、数组
20.1 数组介绍
变量:存储单个元素的内存空间
数组:存储多个元素的连续的内存空间,相当于多个量的集合
数组名和索引:
索引的编号从0开始,于数值索引
索引可支持使用自定义的格式,而不仅是数值格式,即为关联索引,bash4.0 版本之后开始支持
bash 的数组支持稀疏格式(索引不连续)
20.2 声明数组
普通数组:可以不事先声明,直接使用
declare -a ARRAY_NAME
关联数组:必须先声明后使用
declare -A ARRAY_NAME
注意:两者不可相互转换
#实例: [root@centos7 ~]# declare -a name [root@centos7 ~]# name=("1" "2") [root@centos7 ~]# declare -A cource [root@centos7 ~]# cource=([zhang]="18" [wang]="22") [root@centos7 ~]# declare -A name -bash: declare: name: 无法将索引数组转化为关联数组 [root@centos7 ~]# declare -a cource -bash: declare: cource: 无法将关联数组转化为索引数组
20.3 数组赋值
20.3.1 一次只赋值一个元素
ARRAY_NAME[INDEX]=VALUE
#实例: [root@centos7 ~]# class[0]="1" [root@centos7 ~]# class[1]="2"
20.3.2 一次赋值全部元素
ARRAY_NAME=("VAL1" "VAL2" "VAL3" ...)
#实例: [root@centos7 ~]# name=("zhao" "qian" "sun" "li") [root@centos7 ~]# sum=({1..9}) [root@centos7 ~]# file=( *.sh )
20.3.3 只赋值特定元素
ARRAY_NAME=([0]="VAL1" [3]="VAL2" ...)
#实例: [root@centos7 ~]# title=([0]="ceo" [2]="coo")
20.3.4 交互式数组值对赋值
read -a ARRAY
#实例: [root@centos7 ~]# read -a student zhangsan lihong alice
20.4 显示所有数组
declare -a
20.5 引用数组元素
${ARRAY_NAME[INDEX]} #注:如果省略[INDEX]表示引用下标为0的元素
#实例: [root@centos7 ~]# name=("zhao" "qian" "sun" "li") [root@centos7 ~]# echo ${name[0]} zhao [root@centos7 ~]# echo ${name[1]} qian [root@centos7 ~]# echo ${name[2]} sun [root@centos7 ~]# echo ${name[3]} li [root@centos7 ~]# echo ${name[4]} [root@centos7 ~]# echo ${name} zhao
#引用数组所有元素 ${ARRAY_NAME[*]} ${ARRAY_NAME[@]} #数组的长度,即数组中元素的个数 ${#ARRAY_NAME[*]} ${#ARRAY_NAME[@]}
20.6 删除数组
删除数组中的某个元素,会导致稀疏
unset ARRAY[INDEX]
删除整个数组
unset ARRAY
#实例: [root@centos7 ~]# echo ${name[*]} zhao qian sun li [root@centos7 ~]# unset name[3] [root@centos7 ~]# echo ${name[*]} zhao qian sun [root@centos7 ~]# unset name [root@centos7 ~]# echo ${name[*]}
20.7 数组数据处理
20.7.1 数组切片
${ARRAY[*]:offset:number} offset #跳过元素个数 number #要取出的元素个数 #取值移量之后的所有元素 {ARRAY[@]:offset}
#实例: [root@centos7 ~]# sum=({1..9}) [root@centos7 ~]# echo ${sum[*]} 1 2 3 4 5 6 7 8 9 [root@centos7 ~]# echo ${sum[*]:2:3} 3 4 5 [root@centos7 ~]# echo ${sum[*]:6} 7 8 9
20.7.2 向数组追加元素
ARRAY[${#ARRAY[*]}]=value
#实例: [root@centos7 ~]# sum=({1..9}) [root@centos7 ~]# echo ${sum[*]} 1 2 3 4 5 6 7 8 9 [root@centos7 ~]# sum[${#sum[*]}]=10 [root@centos7 ~]# echo ${sum[*]} 1 2 3 4 5 6 7 8 9 10
20.8 关联数组
declare -A ARRAY_NAME ARRAY_NAME=([idx_name1]='val1' [idx_name2]='val2' ...) 注意:关联数组必须先声明再调用
#实例: [root@centos7 ~]# declare -A name [root@centos7 ~]# name[ceo]=zhang [root@centos7 ~]# name[cto]=wang [root@centos7 ~]# name[coo]=li [root@centos7 ~]# echo ${name[ceo]} zhang
20.9 数组范例:
20.9.1 随机生成10个随机数保存于数组中,并找出最大值和最小值
[root@centos7 ~]# cat random_array.sh #!/bin/bash declare -i min max declare -a nums for ((i=0;i<10;i++));do nums[$i]=$RANDOM [ $i -eq 0 ] && min=${nums[0]} && max=${nums[0]} && continue [ ${nums[$i]} -gt $max ] && max=${nums[$i]} [ ${nums[$i]} -lt $min ] && min=${nums[$i]} done echo "all numbers are ${nums[*]}" echo Max is $max echo Min is $min [root@centos7 ~]# bash random_array.sh all numbers are 13193 880 27883 27108 10324 18352 5779 5211 31476 14382 Max is 31476 Min is 880
20.9.2 定义一个数组,数组中的元素对应的值是 /val/log 目录下所有以 .log 结尾的文件;统计出其下标为偶数的文件中的行数之和。
[root@centos7 ~]# cat log_array_file.sh #!/bin/bash declare -a files files=(/var/log/*.log) declare -i lines=0 for i in $(seq 0 $[${#files[*]}-1]);do if [ $[$i%2] -eq 0 ];then let lines+=$(wc -l ${files[$i]} | cut -d' ' -f1) fi done echo "Lines: $lines" [root@centos7 ~]# bash log_array_file.sh Lines: 4
21、字符串处理
21.1 字符串切片
基于偏移量取字符串:
${#var} #返回字符串变量 var 的长度 ${var:offset} #返回字符串变量 var 中从第 offset 个字符后(不包括第 offset 个字符)的字符开始,到最后的部分,offset 的取值在0到${#var}-1 之间(bash4.2后,允许为负值) ${var:offset:number} #返回字符串变量 var 中从第 offset 个字符后(不包括第offset个字符)的字符开始,长度为number 的部分 ${var: -length} #取字符串的最右侧几个字符,取字符串的最右侧几个字符,注意:冒号后必须有一空白字符 ${var:offset:-1ength} #从最左侧跳过 offset 字符,一直向右取到距离最右侧 lengh 个字符之前的内容,即掐头去尾 ${var: -1ength:-offset} #先从最右侧向左取到 length 个字符开始,再向右取到距离最右侧 offset 个字符之间的内容,注意:-length 前空格
#实例:
[root@centos7 ~]# str=abcdef [root@centos7 ~]# echo ${#str} 6 [root@centos7 ~]# str=abcdef你好 [root@centos7 ~]# echo ${#str} 8 [root@centos7 ~]# echo ${str} abcdef你好 [root@centos7 ~]# echo ${str:2} cdef你好 [root@centos7 ~]# echo ${str:2:3} cde [root@centos7 ~]# echo ${str: -3} f你好 [root@centos7 ~]# echo ${str:2:-3} cde [root@centos7 ~]# echo ${str: -5:-2} def
基于模式取字符串:
${var#*word} #其中 word 可以是指定的任意字符,自左而右,查找 var 变量所存储的字符串中,第一次出现的 word,删除字符串开头至第一次出现 word 字符串(含)之间的所有字符 ${var##*word} #同上,贪婪模式,不同的是,删除的是字符串开头至最后一次由 word 指定的字符之间的所有内容 ${var%word*} #其中 word 可以是指定的任意字符,自右而左,查找 var 变量所存储的字符串中,第一次出现的 word,删除字符串最后一个字符向左至第一次出现 word 字符串(含)之间的所有字符 ${var%%word*} #同上,只不过删除字符串最右侧的字符向左至最后一次出现 word 字符之间的所有字符
#实例: [root@centos7 ~]# file="val/log/messages" [root@centos7 ~]# echo ${file#*/} log/messages [root@centos7 ~]# echo ${file##*/} messages [root@centos7 ~]# echo ${file%/*} val/log [root@centos7 ~]# echo ${file%%/*} val [root@centos7 ~]# url=http::/www.yangge.com:8080 [root@centos7 ~]# echo ${url##*:} 8080 [root@centos7 ~]# echo ${url%%:*} http
21.2 查找替换
${var/pattern/substr} #查找 var 所表示的字符串中,第一次被 pattern 所匹配到的字符串,以 substr 替换之 ${var//pattern/substr} #查找 var 所表示的字符串中, 所有能被 pattern 所匹配到的字符串,以 substr 替换之 ${var/#pattern/substr} #查找 var 所表示的字符串中,行首被 pattern 所匹配到的字符串,以 substr 替换之 ${var/%pattern/substr} #查找 var 所表示的字符串中,行尾被 pattern 所匹配到的字符串,以 substr 替换之
21.3 查找并删除
${var/pattern} #删除 var 表示的字符串中第一次被 pattern 匹配到的字符串 ${var//pattern} #删除 var 表示的字符串中所有被 pattern 匹配到的字符串 ${var/#pattern} #删除 var 表示的字符串中所有以 pattern 为行首匹配到的字符串 ${var/%pattern} #删除 var 所表示的字符串中所有以 pattern 为行尾所匹配到的字符串
21.4 大小写转换
${var^^} # 把 var 中的所有小写字母转换为大写 ${var,,} #把 var 中的所有大写字母转换为小写
22、高级变量
22.1 高级变量赋值
#实例: [root@centos7 ~]# title=ceo [root@centos7 ~]# name=${title-yang} [root@centos7 ~]# echo $name ceo [root@centos7 ~]# title= [root@centos7 ~]# name=${title-yang} [root@centos7 ~]# echo $name [root@centos7 ~]# unset title [root@centos7 ~]# name=${title-yang} [root@centos7 ~]# echo $name yang
22.2 高级变量用法-有类型变量
shell 变量一般是无类型的, 但是 bash shell 提供了declare 和 typeset 两个命令用于指定变量的类型,两个命令是等价的
declare [选项]变量名 -r 声明或显示只读变量 -i 将变量定义为整型数 -a 将变量定义为数组 -A 将变量定义为关联数组 -f 显示已定义的所有函数名及其内容 -F 仅显示已定义的所有函数名 -x 声明或显示环境变量和函数,相当于 export -l 声明变量为小写字母declare -l var=UPPER -u 声明变量为大写字母declare -u var=lower
22.3 变量的间接引用
eval 命令将会首先扫描命令行进行所有的置换,然后再执行该命令。该命令适用于那些一次扫描无法实现其功能的变量,该命令对变量进行两次扫描。
#实例: [root@centos7 ~]# n=10 [root@centos7 ~]# echo {1..$n} {1..10} [root@centos7 ~]# eval echo {1..$n} 1 2 3 4 5 6 7 8 9 10
22.4 间接变量引用
如果第一个变量的值是第二个变量的名字,从第一个变量引用第二个变量的值就称为间接变量引用
variable1 的值是 variable2,而 variable2 又是变量名,variable2 的值为 value,间接变量引用是指通过 variable1 获得变量值 value 的行为
bash提供两种格式实现间接变量引用:
eval tempvar=\$$varibale1
tempvar=${!variable1}
#实例: [root@centos7 ~]# name=zhang [root@centos7 ~]# N=name [root@centos7 ~]# echo $N name [root@centos7 ~]# echo \$$N $name [root@centos7 ~]# eval tmp=\$$N [root@centos7 ~]# echo $tmp zhang [root@centos7 ~]# echo ${!N} zhang
[root@centos7 ~]# name=("zhao" "qian" "sun" "li")