零基础Linux操作基础小白快速掌握Shell脚本--流程控制和循环(二)
1.1.1 循环控制语句continue
continue[N]:提前结束第N层的本轮循环,而直接进入下一轮判断;最内层为第1层
格式:
for (());do
循环体1
...
if command2;then
continue
fi
CMDn
....
done
示例: 结束最内层循环
#!/bin/bash
for((i=0;i<10;i++));dofor((j=0;j<10;j++));do[ $j -eq 5 ] && continueecho $jdoneecho ----------------------------done
示例:结束外层循环
#!/bin/bash
for((i=0;i<10;i++));dofor((j=0;j<10;j++));do[ $j -eq 5 ] && continue 2echo $j doneecho ----------------------------done
1.2.1 循环控制语句break
break[N]:提前结束第N层后的全部循环;最内层为第1层,默认为1
示例:结束内层循环
#!/bin/bash
for((i=0;i<10;i++));dofor((j=0;j<10;j++));do[ $j -eq 5 ] && breakecho $j doneecho ----------------------------done
[root@ansible-salve1 shell]# vim info14.sh
[root@ansible-salve1 shell]# bash info14.sh
0
1
2
3
4
----------------------------
0
1
2
3
4
----------------------------
示例:结束外层循环
#!/bin/bash
for((i=0;i<10;i++));dofor((j=0;j<10;j++));do[ $j -eq 5 ] && break 2echo $j doneecho ----------------------------done
[root@ansible-salve1 shell]# vim info14.sh
[root@ansible-salve1 shell]# bash info14.sh
0
1
2
3
4
[root@ansible-salve1 shell]#
案例1:餐厅简易点餐系统
#!/bin/bash sum=0 COLOR='echo -e \033[1;31m' COLOR2='echo -e \033[1;32m' END="\033[0m"while true;doecho -e "\033[33;1m\c"cat <<EOF1)鲍鱼2)满汉全席3)龙虾4)燕窝5)帝王蟹6)退出 EOF echo -e "\033[0m"read -p "请点菜:" MENU case $MENU in 1|4)$COLOR'菜价:$10'$ENDlet sum+=10;; 3|5)$COLOR'菜价:$20'$ENDlet sum+=20;; 2)$COLOR'菜价:$1000'$ENDlet sum+=1000;; 6)$COLOR2"你点菜的总价格是$sum"$ENDbreak;; *)echo "没有这道菜,请重新点单";; esac $COLOR2"你点的菜总价格是$sum"$END done`
案例2:持续监控应用状态
#!/bin/bash
#Fuction:基于While循环持续监控应用状态
while true
doif systemctl status sshd | grep -q "active (running)";thenbreakfisleep 10
done
1.2.2 shift技术
shift命令用于对参数的向左移动,通常用于在不知道传入参数个数的情况下依次遍历每个参数,然后进行相应的处理(常见与Linux中各种程序的启动脚本)。在扫描处理脚本程序的参数时,经常要用到shift命令
shift命令每执行一次,参数序列顺次左移一个位置,$#
的值减1,用于分别处理每个参数,移出去的参数不可再用
注意:$#
表示脚本后跟随的参数总的个数,$n可以获取脚本后跟随的第n个参数的值
[root@nqs22-tps22 ~]# type shift
shift 是 shell 内嵌
[root@nqs22-tps22 ~]# help shift
shift: shift [n]移位位置参数。重命名位置参数 $N+1、$N+2 ... 到 $1、$2 ... 如果没有给定 N,则假设为1.退出状态:返回成功,除非 N 为负或者大于 $#。
参考实例:依次读取输入的参数并打印参数个数:
[root@nqs22-tps22 ~]# vim run.sh
[root@nqs22-tps22 ~]# cat run.sh
#!/bin/bash
while [ $# != 0 ];do
echo "第一个参数为:$1,参数个数为:$#"
shift
done
[root@nqs22-tps22 ~]# sh run.sh a b c d e f
第一个参数为:a,参数个数为:6
第一个参数为:b,参数个数为:5
第一个参数为:c,参数个数为:4
第一个参数为:d,参数个数为:3
第一个参数为:e,参数个数为:2
第一个参数为:f,参数个数为:1
参考实例:将参数从左到右逐个移动
[root@nqs22-tps22 ~]# vim shift.sh
#!/bin/bash
while [ $# -ne 0 ]
do
echo "第一个参数为:$1 参数个数为:$#"
shift
done
[root@nqs22-tps22 ~]# sh shift.sh Lily Lucy Jake Mike
第一个参数为:Lily 参数个数为:4
第一个参数为:Lucy 参数个数为:3
第一个参数为:Jake 参数个数为:2
第一个参数为:Mike 参数个数为:1
[root@nqs22-tps22 ~]#
二、Shell中的数组
2.1 Shell数组的概念
数组是若干数据的集合,其中存放的每一份数据都称为元素。Shell
不限制数组的大小,理论上可以存放无限量的数据,Shell
数组元素的下标也是从0开始计数
获取数组中的元素要使用下标[ ],下标可以是一个整数,也可以是一个结果为整数的表达式;下标必须大于等于0
注意:
Shell
只支持一维数组,不支持多维数组
2.2 Shell数组的定义
2.2.1 数组的基本定义
在Shell
中,用小括号()
来表示数组,数组元素之间用空格来分隔
#arrayname=(1 2 3 4 5)
`输出定义数组中的全部元素
#echo ${arrayname[*]}
#echo ${arrayname[@]}
`输出定义数组中的第二个元素
#echo ${arrayname[0]}
`输出定义数组中的第二个元素
#echo ${arrayname[1]}
`输出定义数组中的元素个数
#echo ${#arrayname[*]}
2.2.2 采用键值对的形式赋值
在Shell
中用小括号将变量括起来,同时采用键值对的形式赋值
#array2=([1]=one [2]=two [3]=three)
echo ${array2[*]} #输出定义数组的所有元素
echo ${array2[@]} #输出定义数组的所有元素
echo ${#array2[@]} #输出定义数组的元素个数
2.2.3 通过分别定义数组变量的方法来定义
#array3[1]=a
#array3[2]=b
#array3[3]=c
`输出定义数组中的全部元素
echo ${array3[@]}
`输出定义数组中的第一个元素
echo ${array3[1]}
2.2.4 动态定义数组数量
动态地定义数组变量,并使用命令的输出结果作为数组的内容
# mkdir -p /array
# touch /array/{1..5}.txt
# ls /array
# array4=($(ls /array))
# echo ${array4[*]}
# echo ${array4[@]}
# echo ${#array4[*]}
2.3 Shell数组的打印
打印单个数组元素: ${数组名[下标]} 。当未指定数组下标时,下标默认从0开始
打印全部数组内容:${数组名[@]}或 ${数组名[*]}
打印数组元素的个数:${#数组名[@]}或 ${#数组名[*]}
2.4 Shell数组的赋值
如果下标不存在,则自动添加一个新的元素;如果下标存在,则覆盖原来的值
2.5 Shell数组的拼接合并
所谓Shell
数组拼接(数组合并),就是将两个数组连接成一个数组
拼接数组的思路是:先利用@
或者*
,将数组扩展成列表,然后再合并到一起,具体格式如下:
array_new=(${array1[@]} ${array2[@]})
array_new=(${array1[*]} ${array2[*]})
`两种方式是等价的,选择其一即可。其中,array1 和 array2 是需要拼接的数组,array_new 是拼接后形成的新数组。
示例:
#!/bin/bash
array1=(1 2 3 4 5)
array2=("https://www.baidu.com" "https://www.hehao.online" "https://www.taobao.com")
array_new=(${array1[*]} ${array2[*]})
echo ${array_new[@]}--->结果
1 2 3 4 5 https://www.baidu.com https://www.hehao.online https://www.taobao.com
2.6 Shell删除数组元素
在Shell
中,使用unset关键字来删除数组元素,具体格式如下:
.6 Shell删除数组元素在Shell中,使用unset关键字来删除数组元素,具体格式如下:
2.7 获取数组某范围的元素
在Shell
中直接通过${数组名[@/*]:起始位置:长度}
获取数组给定范围内元素,返回字符串,中间用空格分开
#!/bin/bash
array=(yoona lucy tom)
echo ${array[*]}
echo ${array[*]:1:2}
echo ${array[@]:0:2}
-->结果为:
yoona lucy tom
lucy tom
yoona lucy
2.8数组元素的替换
${数组名[@/*]/查找字符/替换字符}
该操作不会改变原先数组内容,如果需要修改,使用覆盖
#!/bin/bash
array=(yoona lucy tom)
echo ${array[@]/lucy/lily}
echo ${array[*]}--->结果为:
yoona lily tom
yoona lucy tom
2.9关联数组
Bash支持关联数组,它可以使用字符串作为数组索引,有时候采用字符串索引更容易理解
2.9.1 定义关联数组
首先需要使用声明语句declare
将一个变量声明为关联数组
# declare -A assArray
声明后,可以有两种方法将添加到关联数组中
1.利用内嵌索引-值列表的方法
# assArray=([lucy]=beijing [yoona]=shanghai)
# echo ${assArray[lucy]}--->结果为:
beijing
2.使用独立的索引-值进行赋值
# assArray[lily]=shandong
# assArray[sunny]=xian
# echo ${assArray[sunny]}
-->结果为:xian
2.9.2 列出数组索引
每一个数组都有一个索引用于查找。使用${!数组名[@/*]}
获取数组的索引列表
# echo ${!assArray[*]}
# echo ${!assArray[@]}
2.9.3 获取所有键值对
#! /bin/bash
declare -A cityArray
cityArray=([yoona]=beijing [lucy]=shanghai [lily]=shandong)
for key in ${!cityArray[*]}
doecho "${key} come from ${cityArray[$key]}"
done--->结果为:
lily come from shandong
yoona come from beijing
lucy come from shanghai
2.10 mapfile命令
2.10.1 mapfile命令介绍
mapfile
命令用于从标准输入读取行并赋值到数组
2.10.2 mapfile语法结构
mapfile [-d delim] [-n count] [-O origin] [-s count] [-t] [-u fd] [-C callback] [-c quantum] [array]
选项介绍
`-d delim :将delim设为行分隔符,代替默认的换行符
`-n count :从标准输入中获取最多的count行,如果count为0那么获取全部
`-O origin:从数组下标为origin的位置开始赋值,默认的下标为0
`-s count: 跳过对前count行的读取
`-t : 读取时移除分隔符delim(默认为换行符)
`-u fd:从文件描述符fd中读取
`-C callback:每当读取了quantum行时,调用callback语句
`-c quantum:设定读取的行数为quantum
`array(可选):用于输出的数组名称。如果没有指定数组名称,那么会默认写入到变量名为MAPFILE的数组中# 如果使用-C时没有同时使用-c指定quantum的值,那么quantum默认为5000。
#当callback语句执行时,将数组下一个要赋值的下标以及读取的行作为额外的参数传递给callback语句。
#如果使用-O时没有提供起始位置,那么mapfile会在实际赋值之前清空该数组
2.10.3 使用示例
`dns_list.txt
www.baidu.com
www.taobao.com
www.douyin.com
www.4399.com[root@localhost Shell_Study]# mapfile array < dns_list.txt
[root@localhost Shell_Study]# echo ${#array[*]}
4
[root@localhost Shell_Study]# echo ${array[#]}
[root@localhost Shell_Study]# echo ${array[@]}
www.baidu.com www.taobao.com www.douyin.com www.4399.com
三、Shell中的函数
3.1 Shell函数的定义
Shell函数的本质是一段可以重复使用的脚本代码,这段代码被提前编好了,放在了指定位置,使用时直接调用即可
Shell 中的函数和C++、Java、Python、C# 等其它编程语言中的函数类似,只是在语法细节有所差别。
Shell 函数定义的语法格式如下:
function name() {statements[return value]
}
对各个部分的说明:
function
是 Shell 中的关键字,专门用来定义函数;name
是函数名;statements
是函数要执行的代码,也就是一组语句;return value
表示函数的返回值,其中 return 是 Shell 关键字,专门用在函数中返回一个值;这一部分可以写也可以不写。
由{ }
包围的部分称为函数体,调用一个函数,实际上就是执行函数体中的代码。
函数定义的简化写法
1.
name() {statements[return value]
}
2.
function name() {statements[return value]
}
3.2 Shell函数的调用
调用 Shell 函数时可以给它传递参数,也可以不传递。如果不传递参数,直接给出函数名字即可:
name
如果传递参数,那么多个参数之间以空格分隔:
name param1 param2 param3
不管是哪种形式,函数名字后面都不需要带括号。
和其它编程语言不同的是,Shell 函数在定义时不能指明参数,但是在调用时却可以传递参数,并且给它传递什么参数它就接收什么参数。
3.3 Shell函数详解
Shell中的函数在定义时不能指明参数,但是在调用时却可以传递参数。函数参数是Shell位置参数的一种,在函数内部可以使用$n
来接收,例如:$1
表示第一个参数,$2
表示第二个参数,依次类推
除了$n
,还有另外三个比较重要的变量:
$#
可以获取传递的参数的个数;$@
或者$*
可以一次性获取所有的参数
扩展:在Shell中 @ 与 @与 @与*的区别:
在Shell脚本中,$*和$@是Shell脚本的特殊变量,作用都是获取传递给脚本或函数的所有参数
$@与$*的相同点:当它们没有被双引号包裹时,两者是没有区别的,都代表一个包含接收到的所有参数的数组,各个数组元素都是传入的独立参数
$@与$*的不同点:当被双引号包裹时,$@仍为一个数组,而$*会将所有参数整合成一个字符串
nginx启停脚本案例
#!/bin/bash
start(){
#当封装函数时,不能使用ps命令netstat -anptu | grep nginx &> /dev/nullif [ $? -eq 0 ] thenecho "服务已经启动"elsenginxecho "nignx服务正在启动...OK!"fi
}
stop(){netstat -anptu | grep nginx &> /dev/nullif [ $? -ne 0 ]thenecho "nginx服务已经关闭"elseecho $?nginx -s stopecho "nignx服务正在关闭...OK!"fi
}
reload(){nginx -s reloadecho "nignx服务正在重载...OK!"
}
restart(){nginx -s stop &> /dev/nullnginxecho "nignx服务正在重启...OK!"
}case $1 in
start)start
;;
stop)stop
;;
reload)reload
;;
restart)restart
;;
*)echo "USEAGE: $0 start | stop | reload | restart"
esac
四、Shell高级
4.1 echo 带颜色输出
4.1.1 echo命令介绍
`语法:
echo [-ne] [字符串]/echo [--help] [--version]
补充说明:echo会将输入的字符串送往标准输出。输出的字符串加以空白字符隔开,并在最后加上换行符
参数:
-n:不要再最后自动换行
-e:打开反斜杠ESC转义。若字符串出现以下字符,则特别加以处理,而不会将它当成一般文字输出:\a:发出警告声\b:删除前一个字符\c:最后不加上换行符号\f:换行光标仍停留在原来的位置\n:换行且光标移至行首\r:光标移至行首,但不换行\t:插入tab\v与\f相同\\:插入\\nnn:插入nnn(八进制)所代表的ASCII字符
-E:取消反斜杠ESC转义
-help:显示帮助
-version:显示版本信息
4.1.2 使用echo达到输出有颜色字体的效果
echo -e "\e[背景底色号码;字体颜色号码m 文本内容 \e[0m"
echo -e "\033[背景;字体颜色m 字符串\033[0m"`扩展:使用printf显示颜色字体
printf "\e[背景底色号码;字体颜色号码m 格式化输出符号 \e[0m" "文本内容"
显示黑色背景绿色字体
printf "\e[40;32m %s\n \e[0m" "hello world"
对应的颜色范围:
字体背景颜色范围 40-47
40:黑
41:深红
42:绿
43:黄色
44:蓝色
45:紫色
46:深绿
47:白色字基本颜色号码 30-37
30:黑
31:红
32:绿
33:黄
34:蓝色
35:紫色
36:深绿
37:白色字体高亮颜色号码 90-97
90:黑
91:红
92:绿
93:黄
94:蓝色
95:紫色
96:深绿
97:白色字背景颜色范围 40-47
40:黑
41:深红
42:绿
43:黄色
44:蓝色
45:紫色
46:深绿
47:白色
还有一些特殊的颜色
\33[0m 关闭所有属性
\33[1m 设置高亮度
\33[4m 下划线
\33[5m 闪烁
\33[7m 反显
\33[8m 消隐
\33[30m — \33[37m 设置前景色
\33[40m — \33[47m 设置背景色
\33[nA 光标上移n行
\33[nB 光标下移n行
\33[nC 光标右移n行
\33[nD 光标左移n行
\33[y;xH设置光标位置
\33[2J 清屏
\33[K 清除从光标到行尾的内容
\33[s 保存光标位置
\33[u 恢复光标位置
\33[?25l 隐藏光标
\33[?25h 显示光标
4.2 Shell的八大扩展功能
4.2.1 花括号
在shell脚本中,可以使用括号对字符串进行扩展,我们可以在一对花括号中包含一组以分号分隔的字符串或者字符串序列组成一个字符串扩展,注意最终输出结果以空格分隔,使用该扩展花括号不可以被引号引用,花括号的数量必须是偶数个
[root@localhost ~]# echo {1,5} #对字符串进行扩展
1 5
[root@localhost ~]# echo {hello,world} #对字符串进行扩展
hello world
[root@localhost ~]# echo {a..z} #对字符串序列进行扩展
a b c d e f g h i j k l m n o p q r s t u v w x y z
#字符串后面可以跟一个步长整数,默认为1或-1
[root@localhost ~]# echo {a..z..2}
a c e g i k m o q s u w y
[root@localhost ~]# echo {a..z..3}
a d g j m p s v y
[root@localhost ~]# echo {1..9..3}
1 4 7
[root@localhost ~]# echo {1..9..2}
1 3 5 7 9
[root@localhost ~]# echo "{a..z}" #花括号扩展不能使用引号
{a..z}
[root@localhost ~]# echo t{i,o}p #花括号前后都可以添加可选字符串
tip top
[root@localhost ~]# echo t{o,e{a,m}}p #花括号支持嵌套
top teap temp
#花括号批量操作
[root@localhost ~]# mkdir -p t{o,e{a,m}}p
[root@localhost ~]# touch t{o,e{a,m}}p/{a,b,c,d}e.txt
4.2.2 波浪号
波浪号在Shell脚本中默认代表当前用户家目录
[root@localhost /]# echo ~ #显示当前用户的家目录
/root
[root@localhost /]# echo ~/elk
/root/elk
[root@localhost /]# echo ~elk #显示特定用户的家目录,该用户必须存在
/home/elk
[root@localhost /]# echo ~+ #显示当前工作目录
/
[root@localhost /]# echo ~- #显示前一个工作目录
/root
4.2.3 变量替换
在Shell脚本中我们会使用 对变量进行扩展替换,变量字符可以放到花括号中,这样可以防止需要扩展的变量字符与其他不需要扩展的字符混淆,如果 对变量进行扩展替换,变量字符可以放到花括号中,这样可以防止需要扩展的变量字符与其他不需要扩展的字符混淆,如果 对变量进行扩展替换,变量字符可以放到花括号中,这样可以防止需要扩展的变量字符与其他不需要扩展的字符混淆,如果后面是位置变量且多余一个数字,必须使用{}
[root@localhost ~]# a="hello word"
[root@localhost ~]# echo $a
hello word
[root@localhost ~]# echo ${a}
hello word
[root@localhost ~]# b=a
[root@localhost ~]# echo ${b} #直接返回变量的值
a
[root@localhost ~]# echo ${!b} #间接引用a变量的值
hello word
[root@localhost ~]# c=b
[root@localhost ~]# echo ${!c} #尽可以实现一层简介引用
a
变量替换操作还可以测试变量是否存在及是否为空,若变量不存在或为空,则可以为变量设置一个默认值
Shell脚本支持多种形式的变量测试与替换功能,如下表所示
语法格式 | 功能描述 |
---|---|
${变量:-关键字} | 如果变量未定义或为空,则返回关键字,否则返回变量值 |
${变量:=关键字} | 如果变量未定义或为空,则将关键字赋值给变量,并返回结果,否则直接返回变量值 |
${变量:?关键字} | 如果变量未定义或为空,则通过标准错误显示包含关键字的错误信息,否则返回变量值 |
${变量:+关键字} | 如果变量未定义或为空,则直接返回空,否则返回关键字 |
[root@localhost ~]# echo $bb
[root@localhost ~]# echo ${bb:-bbb}
bbb
[root@localhost ~]# echo $bb[root@localhost ~]# echo ${bb:=bbb}
bbb
[root@localhost ~]# echo $bb
bbb
此外,变量替换还有非常实用的字符串切割与掐头去尾功能
偏移量起始值为0
语法格式 | 功能描述 |
---|---|
${变量:偏移量} | 从变量的偏移量位置开始,切割截取变量的值到结尾 |
${变量:偏移量:长度} | 从变量的偏移量位置开始,切割截取特定长度的变量值 |
${变量#关键字} | 用关键字对变量进行模式匹配,从左到右删除匹配到的内容,关键字可以用*表示,使用#匹配时为最短匹配 |
${变量##关键字} | 用关键字对变量进行模式匹配,从左到右删除匹配到的内容,关键字可以用*表示,使用##匹配时为最长匹配 |
${变量%关键字} | 用关键字对变量进行模式匹配,从右到左删除匹配到的内容,关键字可以用*表示,使用%匹配时为最短匹配 |
${变量%%关键字} | 用关键字对变量进行模式匹配,从右到左删除匹配到的内容,关键字可以用*表示,使用%%匹配时为最长匹配 |
这几种变量替换方式,都不会改变原变量的值
#!/bin/bash
home="hello world linux java spring"
echo ${home:2}
#llo world linux java spring
echo ${home:2:5}
#llo w
echo ${home#he}
# lo world linux java spring
echo ${home#*ja}
# va spring
echo ${home##*r}
# ing
echo ${home%ing}
# hello world linux java spr
echo ${home%i*}
# hello world linux java spr
echo ${home%%i*}
# hello world l
变量内容的统计与替换
语法格式 | 功能描述 |
---|---|
${!前缀字符*} | 查找以指定字符开头的变量名称,变量名之间使用IFS分隔 |
${!前缀字符@} | 查找已指定字符开头的变量名称,@在引号中将被扩展为独立的单词 |
${!数组名称[*]]} | 列出数组中所有下标,*在引号中被扩展为一个整体 |
${!数组名称[@]]} | 列出数组中所有下标,@在引号中被扩展为独立的单词 |
${#变量} | 统计变量的长度,变量可以是数组 |
${变量/旧字符串/新字符串} | 将变量中的旧字符串替换为新字符串,仅替换第一个 |
${变量//旧字符串/新字符串} | 将变量中的旧字符串替换为新字符串,替换所有 |
${变量^匹配字符} | 将变量中的小写替换为大写,仅替换第一个 |
${变量^^匹配字符} | 将变量中的小写替换为大写,替换所有 |
${变量,匹配字符} | 将变量中的大写替换为小写,仅替换第一个 |
${变量,匹配字符} | 将变量中的大写替换为小写,替换所有 |
4.2.4 命令替换
#我们可以通过$(命令)或`命令`方式实现替换[root@localhost /]# echo -e "$(date +%Y-%m-%d;uptime)"
2021-07-1423:47:56 up 29 days, 4:12, 1 user, load average: 0.36, 0.18, 0.14
[root@localhost /]# echo "系统登录人数:$(who | wc -l)"
系统登录人数:1
[root@localhost /]# echo "系统登录人数:`who | wc -l`"
系统登录人数:1
4.2.5 算数替换
通过算数替换阔可以进行算数计算并返回计算结果,算数替换扩展的格式为$(())
,也可以使用$[]
的方式,算数扩展支持嵌套
[root@localhost /]# echo $((i++))
1
[root@localhost /]# echo $((++i))
3
[root@localhost /]# echo $((--i))
2
[root@localhost /]# echo $((18%5)) #取余
3
[root@localhost /]# echo $((2**3)) #幂运算
8
[root@localhost /]# echo $((2>3))
0
[root@localhost /]# echo $((2<3))
1
[root@localhost /]# echo $((2!=3))
1
4.2.6 进程替换
进程替换将进程的返回结果通过命令管道的方式传递给另一个进程
语法格式为:<(命令)或者>(命令)
一旦使用了进程替换功能,系统将会在/dev/fd目录下创建文件描述符文件,通过该文件描述符将进程的输出结果传递给其他进程
Linux系统中可以使用管道将前一个命令输出重定向到文件,但是一旦使用了重定向输出到文件,输出结果无法在屏幕上显示
[root@localhost /]# ls /etc/*.conf > ~/conf.log
[root@localhost /]# cat ~/conf.log
/etc/asound.conf
...后续内容省略...
使用tee
命令既可以重定向到文件,又可以在屏幕上显示输出结果
[root@localhost /]# ls /etc/*.conf | tee ~/conf.log
/etc/dracut.conf
...后续内容省略...
[root@localhost /]# cat ~/conf.log
/etc/dracut.conf
...后续内容省略...
4.2.7 单词切割
单词切割又叫做分词,Shell使用IFS变量进行分词处理。如果没有自定义IFS变量,默认为空格,Tab制表符,换行符
[root@localhost ~]# read -p "输入:" x y z
输入:1 2 3
[root@localhost ~]# echo $x
1
[root@localhost ~]# echo $y
2
[root@localhost ~]# echo $z
3
#自定义IFS变量的值
[root@localhost ~]# IFS=$',' read -p "输入:" x y z
输入:4,5,6
[root@localhost ~]# echo $x
4
[root@localhost ~]# echo $y
5
[root@localhost ~]# echo $z
6
4.2.8 路径替换
除非使用set -f
禁用路径替换,否则bash会在路径和文件名中搜索*、?和[符号,如果找到了这些符号则进行模式匹配的替换。
使用shopt
命令时开启了nocaseglob
选项,则bash的进行模式匹配时不区分大小写,默认区分大小写。
此外还可以开启extglob
选项,让bash支持扩展通配符。
shopt
命令-s选项可以开启特定的Shell属性,-u选项可以关闭特定的Shell属性
[root@localhost shopt]# touch {a,A,b,B}.txt
[root@localhost shopt]# ls a.txt
a.txt
[root@localhost shopt]# shopt -s nocaseglob
[root@localhost shopt]# shopt nocaseglob
nocaseglob on
[root@localhost shopt]# ls B*
b.txt B.txt
[root@localhost shopt]# ls a*
a.txt A.txt
[root@localhost shopt]# shopt -u nocaseglob
[root@localhost shopt]# ls a*
a.txt
[root@localhost shopt]# shopt -s extglob
[root@localhost shopt]# ls !(a.txt|b.txt)
A.txt B.txt
[root@localhost shopt]# shopt -u extglob
[root@localhost shopt]# ls !(a.txt|b.txt)
-bash: !: event not found
basename
和dirname
:
basename
:可以获取一个路径中的文件名
dirname
:仅保留路径,删除文件名
4.3 Shell中的expect
4.3.1 expect介绍
expect 是由Don Libes基于Tcl( Tool Command Language )语言开发的,主要应用于自动化交互式操作的场景,借助Expect处理交互的命令,可以将交互过程如:ssh登录,ftp登录等写在一个脚本上,使之自动化完成。尤其适用于需要对多台服务器执行相同操作的环境中,可以大大提高系统管理人员的工作效率。
`expect 的安装
yum -y install expect
4.3.2 expect命令
`语法格式:
expect [选项] [ -c cmds ] [ [ -[f|b] ] cmdfile ] [ args ]
常用选项
-c :从命令行执行expect脚本,默认expect是交互地执行的
-d :可以输出调试信息expect中相关的命令
spawn:启动新的进程
send:用于向进程发送字符串
expect:从进程接收字符串
interacr:允许用户交互
exp_continue:匹配多个字符串在执行动作后加此命令
set timeout 30:设置超时时间timeout为30s,expect命令阻塞超时时会自动往下继续执行。将timeout配置为-1时表示expect一直阻塞直到与期待的字符串匹配上才继续往下执行。超时时间timeout默认为10s。
[lindex $argv n]:可以在脚本中使用该命令获取在脚本执行时传入的第n个参数。这里argv为传入的参数,另外argv为传入的参数,另外argc表示传入参数的个数,$argv0表示脚本名字。另外我们也可以使用[lrange $argv sn en]命令获取第sn到第en个参数。
4.3.3 expect最常用的语法(tcl语言:模式-动作)
4.3.3.1.expect的单分支语法
[root@localhost ~]# expect
expect1.1> expect "hi" {send "say hi\n"} #捕捉用户输入的hi,然后给用户发送"say hi\n"
hi #这一行是我输入的,由于被我上面定义的语句捕捉到了,下面一行的输出信息就是我之前自定义的
say hi
expect1.2> # 如果不想使用该程序了,可以通过输入"exit"或者ctrl+D来正常退出交互式界面
4.3.3.2.expect的多分支语法
[root@localhost ~]# expect
expect1.1> expect "hi" {send "say hi\n"} "bye" {send "byebye\n"}
bye
byebye
4.3.4 通过实际脚本来理解expect
#!/usr/bin/expect
# 使用expect来解释该脚本
set timeout 30
# 设置超时时间,单位为秒,默认情况下是10s
set host "njdx01.91vps1.com"
#设置远程连接的主机
set port "31026"
#设置SSH端口变量
set username "root"
set pass "Gizakps@1289"
#设置密码
spawn ssh $username@$host -p$port
# spawn:是进入expect环境后才可以执行的expect内部命令,如果没有装expect或者直接在默认的SHELL下执行是找不到spawn命令的。
# 它主要的功能是给ssh运行进程加个壳,用来传递交互指令
expect "*password*" {send "$pass\r"}
#这里的expect也是expect的一个内部命令,这个命令的意思是判断上次输出结果里是否包含"password"的字符串,如果有则立即返回;否则就等待一段时间后返回
interact
#执行完后保持交互状态,把控制权交给控制台,这个时候就可以手工操作了`特别提醒:该脚本不能通过bash 脚本名来运行,而是需要通过
#chmod +x 脚本名
#./脚本名
在上述的示例中,设计到expect中一个非常重要的概念–模式-动作;即上述expect "*password*" {send "$password\r"}
这句代码表达出来的含义。简单的说就是匹配一个模式,就执行对应的做东;匹配到password字符串,就输入密码。你可能回看到这样的代码
expect {"password"{send "$pass\r"exp_continue}eof{send "eof"}
}
其中exp_continue表示循环式匹配,通常匹配之后都会退出语句,但如果有exp_continue则可以不断循环匹配,输入多条命令,简化写法。
很多时候,我们需要传递参数到脚本中,现在通过下面这段代码来看看如何在expect中使用参数:
#!/usr/bin/expect
if {$argv < 3} {puts "Usage:cmd <host> <username> <password>"exit 1
}
set timeout -1
# 表示expect一直阻塞直到与期待的字符串匹配上才继续往下运行
set host [lindex $argv 0]
set username [lindex $argv 1]
set port [lindex $argv 2]
set pass [lindex $argv 3]
spawn ssh $username@$host -p$port
expect "*password*" {send "$pass\r"}
interact# 在expect,$argc表示参数个数,而参数值存放在$argv中,比如取第一个参数就是[index $argv 0],以此类推`特别提醒:该脚本不能通过bash 脚本名来运行,而是需要通过
#chmod +x 脚本名
#./脚本名 参数1 参数2 参数3 参数4
4.3.5 实例
1.传参式登录指定主机
#!/usr/bin/expect -f
set ip [ lindex $argv 0 ]
set pass [ lindex $argv 1 ]
set timeout 10
spawn ssh root@$ip
expect {
"*yes/no" { send "yes\r";exp_continue}//第一次ssh连接会提示yes/no,自动发送yes
"*password:" { send "$pass\r"}
}
interact`chmod +x 脚本名
`./脚本名 参数1 参数2
2.利用expect批量ssh互信
#!/bin/bash
# 判断id_rsa密钥文件是否存在
if [ ! -f ~/.ssh/id_rsa ];thenssh-keygen -t rsa -P "" -f ~/.ssh/id_rsa# -t
elseecho "id_rsa has created ..."
fi# 分发到各个节点,这里分发到host文件中的主机中
while read line
douser=`echo $line | cut -d " " -f 2`ip=`echo $line | cut -d " " -f 1`passwd=`echo $line | cut -d " " -f 4`port=`echo $line | cut -d " " -f 3`expect <<EOFset timeout 10spawn ssh-copy-id $user@$ip -p$portexpect {"*yes/no" { send "yes\n";exp_continue }"password" { send "$passwd\n" }}expect "password" { send "$passwd\n" }
EOF
done < /root/host.txt`/root/host.txt
njdx01.91vps1.com root 21062 Gizakps@1289
xadx01.91vps1.com root 26154 Gizakps@1289
dllt01.91vps1.com root 20270 Gizakps@1289
4.4 Shell中的信号捕捉trap(不用了解)
Linux常用命令trap
用于指定在接收信号后将要采取的动作,常见的用途是在脚本程序被中断时完成清理工作
4.4.1 Linux信号
信号(IPC)最初是UNIX系统响应某些状况而产生的事件,进程在接收到信号时会采取相应的行动。简单来说信号是操作系统(内核)响应某些条件而产生的一个事件(给进程)。进程之间无法通信,可以使用信号来解决。
信号是由于某些错误条件而生成的,如内存段冲突,浮点处理器错误或非法指令等。他们由 shell 和终端处理器生成来引起中断,他们还可以作为在进程中传递消息或修改行为的一种方式,明确地由一个进程发送给另外一个进程
Linux常见的信号有:
信号 | 值 | |
---|---|---|
1 | SIGHP | 挂起进程 |
2 | SIGINT | 终止进程 |
3 | SIGQUIT | 停止进程 |
9 | SIGKILL | 无条件终止进程 |
15 | SIGTERM | 尽可能终止进程 |
17 | SIGSTOP | 无条件停止进程,但不是终止进程 |
18 | SIGTSTP | 停止或暂停进程,但不终止进程 |
19 | SIGCONT | 继续运行停止的进程 |
ctrl+c
组合键会产生SIGINT
信号,ctrl+z
组合键会产生SIGTSTP
信号
kill -0 pid
不发送任何信号,但是系统会进行错误检查。该命令可以用来检查一个进程是否存在,若存在,即进程正在运行,执行echo $?
会返回0.若不存在,即进程已停止运行,执行echo $?
会返回1
4.4.2 trap命令
trap
命令允许你来指定Shell脚本要监视并拦截的Linux信号
`语法格式:
trap commands signal1 [signal2 signal3 ....]
# 如果当前脚本进程收到上述signals信号中的一个,就会执行commands命令trap '' 信号 # 忽略信号的操作
trap '-' 信号 # 恢复原信号的操作
trap -p # 列出自定义信号操作
trap finish EXIT #当脚本退出时.执行finish函数,当然这个"finish"这个名字可以自定义
脚本示例
#!/bin/bash
trap "echo 'Sorry~I have trapped Ctrl+c'" SIGINT
echo "This is a test script"
count=1
while [ $count -le 10 ];doecho "Loop $count"sleep 1count=$[ $count +1 ]
done
echo "The end"