第十二章 脚本进阶

1.SHELL脚本编程进阶

1.1 循环

1.1.1 循环执行介绍

在这里插入图片描述

将某代码段重复运行多次,通常有进入循环的条件和退出循环的条件

重复运行的次数

  • 循环次数事先已知
  • 循环次数事先未知

常见的循环的命令:for,while,until

1.2 for循环

格式1:

for 变量名 in 列表;do
	循环体;
done

格式2:

for((exp1;exp2;exp3));do
	循环体
done	

例:输出1~100

for i in {1..100};do
	echo ${i};
done

for((i=1;i<=100;i++));do
	echo ${i};
done	

执行机制:依次将列表中的元素赋值给“变量名”;每次赋值后执行一次循环体;直到列表中的元素耗尽,循环结束。

for循环列表生成方式:

  • 直接给出列表
  • 整数列表

1.1.2 三个范例

1.创建YYYY-MM-DD文件夹,在YYYY-MM-DD下随机创建日志文件

#!/bin/bash
for i in {1..365};
do
	DIR=`date -d "-$i days" +%F`
	mkdir /data/test/$DIR;
	for j in {1..10};
	do 
		touch /data/test/$DIR/$RANDOM.log
	done
done

2.将YYYY-MM-DD文件下得文件移动到YYYY-MM/DD下

#!/bin/bash

DIR=/data/test
cd ${DIR}

for DIR in *;do
	YYYY_MM=`echo ${DIR} | cut -d- -f1,2`;
	DD=`echo ${DIR} | cut -d- -f3`;
	if [ -d /data/test/${YYYY_MM}/${DD} ]; then
		mkdir /data/test/${YYYY_MM}/${DD} -p
	fi
	mv /data/test/$DIR/* /data/test/${YYYY_MM}/${DD}
done

3.99乘法表

#!/bin/bash

for i in {1..9};do
	for j in `seq $i`;do
		x=$[i*j]
		echo -e "${i}x${j}=${x} \c\t"; 
	done
	echo
done

#格式二
for((i=1;i<=9;i++)); do 					for((j=1;j<=i;j++)); do 
		echo -e "${i}*${j}=$[i*j]\t\c"; 	done;
	echo""; 
done;

1.2.3 练习:用for实现

1、判断/var/目录下所有文件的类型

2.添加10个用户user 1-user10,密码为8位随机字符

3./etc/rc.d/rc3.d目录 下分别有多个以K开头和以S开头的文件:分别读取每个文件,以K开头的输出为文件加stop,以S开头的输出为文件名加start,如K34filename stop 66filename start

4、编写脚本,提示输入正整数n的值,计算1+2…+n的总和

5、计算100以内所有能被3整除的整数之和

6、编写脚本,提示请输入网络地址,如192.168.0.0,判断输入的网段中主机在线状态

7.打印九九乖法表

8、在/testdir目录 下创建10个html文件,文件名格式为数字N (从1到10) 加随机8个字母,如: 1AbCdeFgH.html

9、打印等腰三角形

10、猴子第一天摘下若干个桃子,当即吃了一半,还不癌,又多吃了一个。第二天早上又将剩下的桃子吃掉一半,又多吃了一个。以后每天早上都吃了前一天剩下的一半零-个。到第10天早上想再吃时,只剩下一个桃子了。求第一天共摘了多少?

1.2 while循环

格式:

while CONDITION;do
	循环体
done	

说明:

CONDITION:循环控制条件;进入循环之前,先做一次判断;每一次循环之后会再次做判断;条件为”true“,则执行一次循环;直到条件测试状态为”false“终止循环,因此:CONDTION一般应该有循环控制变量;而此变量值会在循环体不断地被修正

进入条件:CONDITION为true

退出条件:CONDITION为false

1.2.1 范例

​ 磁盘大于80%发送邮件

#!/bin/bash
SECOND=60
SIZE=80
#':' 和 'true'一样地意义
while :;do
	disk=`df | sed -rn "/^\/dev\/sd/s#.* ([0-9]+)%.*#\1#p"`
	echo ${disk}
	if [ ${disk} -ge ${SIZE} ] ;then 
		echo "disk 要满了" | `mail -s disk_warning 1696741038@qq.com`
	fi
	sleep ${SECOND};
done

1.2.2 无限循环

格式1:

while true;do
	循环体
done

格式2:

while : do
	循环体
done	

1.2.3 while 特殊用法

while循环的特殊用法,遍历文件或文本地每一行

格式:

while read line; do
	循环体
done < /PATH/FROM/SOMEFILE	

例:


#!/bin/bash

while read line; do
        if [[ ${line} =~ nologin$ ]] ;then
                echo $line | cut -d: -f1,7
        fi
done < /etc/passwd

说明,依次读取/PATH/FROMSOMFILE文件中的每一行,且将行赋值给变量line

范例:

​ read命令

[root@centos7: ~]#echo magedu | read X; echo $X

[root@centos7: ~]#echo magedu | while read X;do echo $X;done
magedu
[root@centos7: ~]#echo magedu | { read X;echo $X; }
magedu
[root@centos7: ~]#echo magedu | ( read X;echo $X; )
magedu
[root@centos7: ~]#echo mage wang zhang | ( read X Y Z; echo $X $Y $Z  )
mage wang zhang
[root@centos7: ~]#echo mage wang zhang | while read X Y Z; do echo $X $Y $Z;done
mage wang zhang


范例:

​ 检查磁盘使用率,发送邮件

#!/bin/bash

EMAIL=1696741038@qq.com
TIME=10
WARNING=80

df | sed -nr "/^\/dev\/sd/s#^([^ ]+) .* ([0-9]+)%.*#\1 \2#p" | while read DEVICE use;
do
	if [ ${use} -gt ${WARNING} ]; then
		echo "${DEVICE} while be full,use:${use}" | mail -s "DISK WARNING" ${EMAIL}
	fi

done

范例:

​ 拒绝黑客ssh攻击

#!/bin/bash
lastb | sed -rn '/ssh:/s@.*([0-9.]{1,3}{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    

1.2.4 练习:用while实现

1.编写脚本,求100以内所有正奇数之和

2、编写脚本,提示请输入网络地址,如192.168.0.0, 判断输入的网段中主机在线状态,并统计在线和离线主机各多少

3、编写脚本,打印九九乘法表

4.编写脚本,利用变是RANDOM生成10个随机数字,输出这个10数字,并显示其中的最大值和最小值

5、编写脚本,实现打印国际象棋棋盘

6、后续六个字符串: efbaf275cd. 4be9c40b8b、 44b2395c46、 f8c8873ce0、 b902c16c8b. ad865d2f63是通过对随机数变量RANDOM随机执行命令: echo SRANDOM |mdsum|cut -C1-10后的结果, 请破解这些字符串对应的RANDOM值

1.3 until循环

格式

until CONDITION;do
	循环体
done	

说明:

进入条件:CONDITION为false

退出条件:CONDITION为true

1.3.1 无限循环

until false;do
	循环体
done	

1.4 循环控制语句

1.4.1 循环体控制语句:continue

continue [N]:提前结束第N层地本轮循环,而直接进入下一轮判断;最内层为第1层

格式:

while CONDITION1; do
	CMD1
	...
	if CONDITION2; then
		continue
	fi
    CMDn
    ...
done    

1.4.2 循环控制语句 break

break [N]:提前结束第N层循环,最内层为1层

格式:

while CONDITION1; do
	CMD1
	...
	if CONDITION2; then
		break;
	fi
    CMDn
    ...
done    

范例:

#!/bin/bash
sum=0
while true;
do
cat << EOF
1) 鲍鱼
2) 满汉全席
3) 龙虾
4) 燕窝
5) 帝王蟹
6) 退出
EOF
read -p "请点餐" MENU
case $MENU in

1|4)
	echo 价格10元
	let sum+=10
	;;
3|5)
	echo 价格20元
	let sum+=20
	;;
2)
	echo 价格20000元
	let sum+=20000;
	;;
6)
	echo "退出"
	echo "总价钱$sum"
	break
	;;
*)
	echo "点错了没有这道菜"
esac

done

1.4.3 循环控制shift命令

shift [n] 用于将参量列表list 左移指定次数,缺省为左移一次。

参数列表list一旦被移动,最左端地那个参数就从列表中删除。while循环遍历位置参量列表时,常用到 shift

1.4.3.1 范例:

​ 打印参数

#!/bin/bash
while [ "$1" ]; do
	echo $1
	shift
done

创建用户

#!/bin/bash
if [ $# -eq 0 ];
then 
	echo "请输入要创建的用户"
	exit 100;
else
	echo "开始创建用户"
fi

while [ "$1" ];do
	if id $1 &> /dev/null;then
		echo $1 is exist
	else
		useradd $1
	fi
	shift
done
echo "ALL user is  created"
1.4.3.2 练习

1、每隔3秒钟到系統上获取已经登录的用户的信息;如果发现用户hacker登录,则将登录时间和主机记录于日志var/log/login.log中,并退出脚本

2、随机生成10以内的数字,实现猜字游戏,提示比较大或小,相等则退出

#!/bin/bash
NUM=$[RANDOM%9]
echo "输入0 - 9 之前地数字"
while read -p "guess num:" guess;
do
	if [ ${NUM} -eq ${guess} ]; 
	then
		echo "恭喜猜对了"
		break;
	elif [ ${NUM} -lt ${guess} ]
	then 
		echo "数字猜大";
	else
		echo "数字猜小了";
	fi	
done

3.用文件名做为参数,統计所有参数文件的总行数

4、用二个以上的数字为参数,显示其中的最大值和最小值

1.5 select循环与菜单

格式:

select variable in list; do
	循环体命令
done

说明:

  • select 循环主要用于创建菜单,按数字顺序排列的菜单项显示在标准错误上,并显示PS3提示符,等待用户输入
  • 用户输入菜单列表中的某个数字,执行相应的命令
  • 用户输入被保存在内置变量REPLY中
  • select是个无限循环,因此要记住用break命令退出循环,或用exit命令终止脚本。也可以按ctrl + c退出循环
  • select经常和case联合使用
  • 与for循环类似,可以省略in list,此时使用位置参量

范例:

#!/bin/bash
PS3="请点菜(1-6):"
select MENU in 鲍鱼 满汉全席 龙虾 燕窝 帝王蟹 退出; do
	case $REPLY in
	1|4)
		echo "价格是:\$10";
		;;
	3|5)
		echo "价格是:\$20";
		;;
	2)
		echo 价格是:\$1000;
		;;
	6)
		break;
		;;
	*)
		echo "请选择正确的菜";
		;;
	esac
done

范例:

​ 修改系统部分属性(网卡修改后要修改/etc/sysconfig/network-scripts/ifcfg-ens33 配置文件名和内容,重启网卡服务 systemctl restart network)

#!/bin/bash

PS3="请输入相应的编号:(1-5):"

select menu in 禁用SELinux 关闭防火墙  修改提示符 修改网卡名称 退出; do
	case ${REPLY} in
	1)
		sed -i.bak 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config
		echo “重启生效”;
		;;
	2)
		systemctl disable --now firewalld
		echo "防火墙已禁用"
		;;
	3)
		echo "PS1='\[\e[01;32m\][\u@\h: \W]\$\[\e[00m\]'" > /etc/profile.d/reset.sh
		echo "请重新登录"
		;;
	4)
		sed -i.bak '/GRUB_CMDLINE_LINUX=/s#"$# net.ifnames=0"#' /etc/default/grub 
		grub2-mkconfig -o /boot/grub2/grub.cfg
		echo "网卡名称修改完成,重新启动才能生效"
		;;
	5)
		exit;	
		;;
	*)
		echo "请输入正确的数字"
	esac
done

2 函数介绍

函数function是由若干条shell命令组成的语句快,实现代码重用和模块化编程。

它与shell程序形式上是相似的,不同的它不是一个单独的进程,不能独立运行,而是shell程序的一部分

函数和shell程序比较相似,区别在于:shell程序在子shell中运行,而shell函数在当前shell中运行。因此在当前shell中,函数可对shell中变量进行修改

2.1 管理函数

函数由两部分组成:函数名和函数体

帮助参看:help function

2.1.1 定义函数

#语法一:
func_name(){
	函数体
}

#语法二
function func_name{
	函数体
}

#语法三
function func_name(){
	函数体
}

2.1.2 查看函数

#查看当前已定义的函数名
declare -F
#查看当前已定义的函数定义
declare -f
#查看指定当前已定义的函数名
declare -f func_name
#查看当前已定义的函数名定义
declare -F func_name

2.1.3 删除函数

格式:

unset func_name

2.2 函数调用

函数的调用方式

  • 可在交互式环境下定义函数
  • 可将函数放在脚本文件中作为它的一部分
  • 可放在只包含函数的单独文件中

调用:函数只有被调用才会执行,通过给定函数名调用函数,函数名出现的地方,会被自动替换为函数代码。

函数的生命周期:被调用时创建,返回时终止

2.2.1 交互式环境调用函数

交互式环境下定义和使用函数

范例:

[root@centos7: function]#dir(){
> ls -l
> }
[root@centos7: function]#dir
total 8
-rw-r--r-- 1 root root  48 Apr 13 21:39 demo.sh
-rw-r--r-- 1 root root 231 Apr 13 21:37 test_function.sh

2.2.2 在脚本中定义及使用函数

函数在使用前必须定义,因此应将函数定义放在脚本开始部分,直至shell首次发现它后才能使用,调用函数仅使其函数名即可

[root@centos7: function]#cat test1.sh 
#!/bin/bash
function echoHello(){
	echo hello world;
}

echoHello
[root@centos7: function]#bash test1.sh 
hello world


范例:

#!/bin/bash

function disableSELinux(){
	sed -i.bak 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config
	echo “重启生效”;
}

function closeFireWall(){
	systemctl disable --now firewalld
        echo "防火墙已禁用"
}

function setPS1(){
	 echo "PS1='\[\e[01;32m\][\u@\h: \W]\$\[\e[00m\]'" > /etc/profile.d/reset.sh
         echo "请重新登录"
}

function setNetWorkName(){
	sed -i.bak '/GRUB_CMDLINE_LINUX=/s#"$# net.ifnames=0"#' /etc/default/grub
        grub2-mkconfig -o /boot/grub2/grub.cfg
        echo "网卡名称修改完成,重新启动才能生效"
}

PS3="请输入相应的编号:(1-5):"

select menu in 禁用SELinux 关闭防火墙  修改提示符 修改网卡名称 退出; do
	case ${REPLY} in
	1)
		disableSELinux
		;;
	2)
		closeFireWall
		;;
	3)
		setPS1
		;;
	4)
		setNetWorkName
		;;
	5)
		exit;	
		;;
	*)
		echo "请输入正确的数字"
	esac
done

2.2.3 使用函数文件

可以将经常使用的函数存入一个单独的函数文件,然后将函数文件载入shell,再进行调用函数。文件名可任意选取,但最好与相关人物有某种联系,例如:functions

一旦函数文件载入shell,就可以在命令行或脚本中调用函数。可以使用delcare -f或set命令查看所有定义的函数,其输出列表包括已经载入shell的所有函数

若要改动函数,首先用unset命令从shell中删除函数。改动完毕后,再重新载入此文件

实现函数文件的过程:

1.创建爱你函数文件,只存放函数的定义

2.在shell脚本或交互式shell中调用函数文件,格式如下:

filename 或 source filename

范例:

​ 使用 /etc/init.d/functions 下的 action 函数

#!/bin/bash

. /etc/init.d/functions

function disableSELinux(){
	sed -i.bak 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config
	action  “重启生效”;
}

function closeFireWall(){
	systemctl disable --now firewalld
        action "防火墙已禁用"
}

function setPS1(){
	 echo "PS1='\[\e[01;32m\][\u@\h: \W]\$\[\e[00m\]'" > /etc/profile.d/reset.sh
         action "请重新登录"
}

function setNetWorkName(){
	sed -i.bak '/GRUB_CMDLINE_LINUX=/s#"$# net.ifnames=0"#' /etc/default/grub
        grub2-mkconfig -o /boot/grub2/grub.cfg
        action "网卡名称修改完成,重新启动才能生效"
}
PS3="请输入相应的编号:(1-5):"

select menu in 禁用SELinux 关闭防火墙  修改提示符 修改网卡名称 退出; do
	case ${REPLY} in
	1)
		disableSELinux
		;;
	2)
		closeFireWall
		;;
	3)
		setPS1
		;;
	4)
		setNetWorkName
		;;
	5)
		exit;	
		;;
	*)
		action "请输入正确的数字" false
	esac
done

2.3 函数返回值

函数的执行结果返回值:

  • 使用echo等命令进行输出
  • 函数体中调用命令的输出结果

函数的退出状态码:

  • 默认取决于函数中执行的最后一条命令的退出状态码

  • 自定义退出状态码,其格式为:

    return 从函数中返回,用最后状态命令决定返回值

    return 0 无错误返回

    return 1-255 有错误返回

2.4 环境函数

类似于环境变量,也可以定义环境函数,使子进程也可使用父进程定义的函数

定义环境函数:

export -f function_name
declare -xf function_name

查看环境函数:

export -f
declare -xf

5.5 函数参数

函数可以接受参数:

  • 传递参数给函数:在函数名后面以空白分隔给定参数列表即可,如:testfunc arg1 arg2 …
  • 在函数体当中,可使用$1, 2 , . . . 调 用 这 些 参 数 ; 还 可 以 使 用 2,... 调用这些参数;还可以使用 2,...使@, ∗ , *, ,#等特殊变量

5.6 函数变量

变量作用域:

  • 普通变量:只有在当前shell进程有效,为执行脚本会启动专用子shell进程;因此,本地变量的作用范围是当前shell脚本程序文件,包括脚本中的函数
  • 环境变量:当前shell和子shell有效
  • 本地变量:函数的生命周期;函数结束时变量被自动销毁

注意:

  • 如果函数中定义了普通变量,且名称和局部变量相同,则使用本地变量
  • 由于普通变量和局部变量会冲突,建议在函数中只使用本地变量

在函数中定义本地变量的方法

local NAME=VALUE

5.7 函数递归

函数递归:函数直接或间接调用自身,注意递归层数,可能会陷入死循环。

递归示例:

阶乘是基斯顿·卡曼于1808年发明的运算符号,是数学术语, 一个正整数的阶乘(factorial) 是所有小于及等于该数的正整数的积,并且有0的阶乘为1,自然数n的阶乘写作n!。n!=1x2x3x4x…xn

阶乘亦可以递归方式定义:0!=1,n!=(n-1)! *n

n!=n(n-1)(n-2)…1=n(n-1)!=n(n-1)(n-2)!

范例:fact.sh

#!/bin/bash
function fact(){
	if [ $1 -eq 0 -o $1 -eq 1 ]; then
		echo 1
	else
    	echo $[$1*$(func $[$1 -1])]
    fi
}
fact $1

fork炸弹是一种恶意程序,它的内部是一个不断在fork进程的无限循环,实质是一个简单的递归程序。由于程序是递归的,如果没有任何限制,这会导致这个简单的程序迅速耗尽系统里面的所有资源

函数实现

:(){ :|:& };:

#解释管道符号会开启子进程
bomb(){ bomb | bomb & }; bomb

脚本实现

cat Bomb.sh
#!/bin/bash
./$0|./$0&

练习

1.编写函数,实现OS的版本判断

2.编写函数,实现取出当前系统eth0的IP地址

3.编写函数,实现打印绿色OK和红色FAILED

4.编写函数,实现判断是否无位置参数,如无参数,提示错误

5.编写函数,实现两个数字做为参数,返回最大值

6.编写服务脚本/root/bin/testsrv.sh,完成如下要求

​ (1)脚本可接受参数: start, sfop, restart, status

​ (2)如果参数非此四者之一,提示使用格式后报错退出

​ (3)如是start:则创建/var/lock/subsys/SCRIPT_NAME,并显示”启动成功”。考虑:如果事先已经启动过一次,该如何处理?

​ (4)如是stop:则删除/var/lock/subsys/SCRIPT_NAME,并显示"停止完成”考虑:如果事先已然停止过了,该如何处理?

(5)如是restart,则先stop, 再start考虑:如果本来没有start,如何处理?

(6)如是status,则如果/var/lock/subsys/SCRIPT_NAME文件存在,则显示"SCRIPT. NAME is runn…".如果/var/lock/subsys/SCRIPT _NAME文件不存在,则显示"SCRIPT_NAME is stopped…"

(7)在所有模式下禁止启动该服务,可用chkconfig 和service命令管理。说明: SCRIPT_NAME为当前脚本名

7.编写脚本/root/bin/copycmd.sh

​ (1)提示用户输入一个可执行命令名称

​ (2)获取此命令所依赖到的所有库文件列表

​ (3)复制命令至某目标目录(例如/mnt/sysroot)下的对应路径下如: /bin/bash ==> /mnt/sysroot/bin/bash

​ /usr/bin/passwd ==> /mnt/sysroot/usr/bin/passwd

​ (4)复制此命令依赖到的所有库文件至目标目录下的对应路径下:如: /lib64/ld-inux-x86-64.so.2 ==>/mnt/sysrot/lib64/ld-linux-x86-64.so.2

​ (5)每次复制完成一个命令后, 不要退出,而是提示用户键入新的要复制的命令,并重复完成上述功能;直到用户输入quit退出.

8.斐波那契数列又称黄金分割数列,因数学家列昂纳多斐波那契以兔子繁殖为例子而引入,故又称为“兔子数列",指的是这样一个数列: 0、1. 1、2、3、5、8、13、21. 34、… 斐波纳契数列以如下被以递归的方法定义: F (0) =0,F (1) =1, F (n) =F(n-1)+F(n-2) (n≥2) ,利用函数,求阶斐波那契数列

9.汉诺塔(又称河内塔)问题是源于印度一个古老传说。 大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘,利用函数,实现N片盘的汉诺塔的移动步骤

6其他脚本相关工具

6.1 信号捕捉 trap

trap ‘触发指令’ 信号

​ 进程收到系统发出的指定信号后,将执行自定义指令,而不会执行原操作

trap ‘’ 信号

  • 忽略信号的操作

trap ‘-’ 信号

  • 还原原信号的操作

trap -p

  • 列出自定义信号操作

trap finish EXIT

  • 当脚本退出时,执行finish函数

范例:

#!/bin/bash
trap 'echo "signal:SIGINT"' int quit
trap -p

for((i=0;i<=10;i++))
do
	sleep 0.5
	echo ${i};
done
trap '' int
trap -p

for i in {11..20}
do
	sleep 0.5
	echo ${i};
done

trap '-' int
trap -p
for ((i=21;i<=30;i++))
do 
	sleep 0.5
	echo $i;
done

范例

#!/bin/bash

function finish(){
	echo "finish" | tee -a finish.log
}
trap finish exit
while :;do
	echo running;
	sleep 0.5;
done

6.2 创建临时文件 mktemp

mktemp命令用于创建并显示临时文件,可避免冲突

格式:

mktemp [option]... [TEMPLATE]

说明:TEMPLATE:filenameXXX,X至少要出现三个

常见选项:

  • d 创建临时目录
  • -p DIR 或 --tmpdir=DIR 指明临时文件所存放目录位置

范例:

#创建临时文件
mktemp /tmp/testXXX
#创建临时文件夹
tmpdir=`mktemp -d /tmp/testdirXXX`
#创建临时文件
mktemp --tmpdir=/testdir testXXXXXX

6.3 安装复制文件 install

install命令格式:

install [OPTION]... [-T] SOURCE DEST 单文件
install [OPTION]... SOURCE... DIRECTORY (拷贝)
install [OPTION]... -t DIRECTORY SOURCE...
install [OPTION]... -d DIRECTORY... 创建空目录

选项:

  • -m MODE,默认755
  • -o OWNER
  • -g GROUP

范例:

install -m 700 -o wang -g admins srcfile desfile
install -m 770 -d /testdir/installdir

6.4 交互式转化批处理工具 expect

expect 是由Don Libes基于Tcl(Tool Command Language)语言开发的,主要应用于自动化交互式操作的场景,借助expect处理交互的命令,可以将交互过程如:ssh登录,ftp登录等写在一个脚本上,使之自动化完成。尤其适用于需要对多台服务器执行相同操作的环境中,可以大大提高系统管理员的工作效率

expect 语法:

expect [选项] [-c cmds] [[-[f|b]] cmdfile] [args]

常见选项:

  • -c:从命令行执行expect脚本,默认expect是交互式地执行地
  • -d:可以输出输出调试信息

示例:

#安装
yum install expect
#监听('expect "\n")回车,然后输出("pressed enter \n")
expect -c 'expect "\n" {send "pressed enter \n"}expect -d ssh.exp

expect中相关命令

  • spawn 启动新的进程
  • expect 从进程接收字符串
  • send 用于向进程发送字符串
  • interact 允许用户交互
  • exp_continue 匹配多个字符串在执行动作后加此命令

expect最常用地语法(tcl语言:模式-动作)

单一分支模式语法:

[root@centos7: data]#expect
expect1.1> expect "hi" {send "you said hi \n"}
hhh
hi
you said hi 
expect1.2> 

匹配到hi后,会输出“you said hi”,并换行

多分支模式语法:

[root@centos7: data]#expect
expect1.1> expect "hi" {send "you said hi \n"} "hehe" {send "you said hehe \n"} "bye" {send "Good bye \n"}
hi
you said hi 
expect1.2> expect "hi" {send "you said hi \n"} "hehe" {send "you said hehe \n"} "bye" {send "Good bye \n"}
hehe
you said hehe 
expect1.3> expect "hi" {send "you said hi \n"} "hehe" {send "you said hehe \n"} "bye" {send "Good bye \n"}
bye
Good bye 
expect1.4>

匹配hi,hehe,bye任意字符串时,执行相应输出。等同如下:

expect {
	"hi" {send "you said hi \n"}
	"hehe" {send "you said hehe \n"}
	"bye" {send "Good bye \n"}
}

范例1:(注意expect和’{‘有空格;“yes/no” 和’{‘也有空格)

#!/usr/bin/expect
spawn scp /etc/fstab 192.168.2.101:/root 
expect {
	"yes/no" { send "yes\n"; exp_continue }
	"password" { send "soft01\n" }
} 
expect eof

范例2:

#!/usr/bin/expect
spawn ssh 192.168.2.101
expect {
	"yes/no" {send "yes\n";exp_continue}
	"password" {send "soft01\n"}
}
interact

范例3:expect变量

#!/usr/bin/expect
set ip 192.168.2.101
set user root
set password soft01
set timeout 10
spawn ssh ${user}@$ip
expect {
	"yes/no" {send "yes\n";exp_continue}
	"password" {send "${password}\n"}
}
interact

范例4:expect位置参数

#!/usr/bin/expect
set ip [lindex $argv 0]
set user [lindex $argv 1]
set password [lindex $argv 2]
spawn ssh $user@$ip
expect {
	"yes/no" {send "yes\n"; exp_continue}
	"password" {send "$password\n"}
}
interact

[root@centos7: expect]# ./ssh3.exp 192.168.2.101 root soft01

范例5:expect执行多个命令

#!/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 "$password\n"}
}
expect "]#" {send "useradd haha\n"}
expect "]#" {send "echo 123456| passwd --stdin haha\n"}
send "exit\n"
expect eof
[root@centos7: expect]# ./ssh4.exp 192.168.2.101 root soft01

范例6:shell脚本调用expect

#!/bin/bash
ip=$1
user=$2
password=$3
expect << EOF
set timeout 20
spawn ssh $user@$ip
expect {
	"yes/no" {send "yes\n";exp_continue}
	"password" {send "$password\n"}
}
expect "]#" {send "useradd haha\n"}
expect "]#" {send "echo 123456| passwd --stdin haha\n"}
send "exit\n"
expect eof
EOF
[root@centos7: expect]# ./ssh5.exp 192.168.2.101 root soft01

范例7:shell脚本利用循环调用expect,批量主机创建账号

#!/bin/bash
NET=192.168.2
user=root
password=123456

for ID in 101 102 103; do
	ip=${NET}.${ID}
	expect << EOF
	set timeout 20
	spawn ssh $user@$ip
	expect {
		"yes/no" {send "yes\n"; exp_continue}
		"password" {send "${password}"}
	}
	expect "]#" {send "useradd test22\n"}
	expect "]#" {send "echo 123456| passwd --stdin test22\n"}
	expect "]#" {send "exit\n"}
	expect eof
	EOF
done

7 数组

7.1 数组介绍

变量:存储单个元素的内存空间

数组:存储多个元素的连续内存空间,相当于多个变量的集合

数组名和索引

  • 索引的编号是从0开始,属于数值索引
  • 索引可支持使用自定义的格式,而不仅是数值格式,即为关联索引,bash4.0版本之后开始支持
  • bash的数组支持稀疏格式(索引不连续)

7.2 声明数组

# 普通数组:可以不事先声明,直接使用
declare -a ARRAY_NAME
# 关联数组:必须先声明,再使用
declare -A ARRAY_NAME 关联数组

注意:两者不可相互转换

7.3 数组赋值

数组元素的赋值

(1)一次只赋值一个元素

ARRAY_NAME[INDEX]=VALUE

范例:

weekdays[0]="Sunday"
weekdays[4]="Thursday"

(2)一次赋值全部元素

ARRAY_NAME=("VAL1" "VAL2" "VAL3" ...)

范例:

nums=({1..10})
alpha=({a..z})
file=(*.sh)
[root@centos7: ~]#declare -a
declare -a nums='([0]="1" [1]="2" [2]="3" [3]="4" [4]="5" [5]="6" [6]="7" [7]="8" [8]="9" [9]="10")'
declare -a student='([0]="xiaoming" [1]="xiaohong" [2]="ailic" [3]="bob")'

(3)只赋值特定元素

ARRAY_NAME=([0]="VAL1" [3]="VAL2" ...)

(4)交互式数组值对赋值

read -a ARRAY

7.4 显示所有数组

显示所有数组:

declare -a

范例:

root@centos7: ~]#declare -a
declare -a name='([0]="0mage" [2]="2wang")'
declare -a names='([0]="1" [1]="2")'
declare -a student='([0]="xiaoming" [1]="xiaohong" [2]="ailic" [3]="bob")'
declare -a title='([0]="title1" [1]="title2" [2]="title3")'

7.5 引用数组

引用数组元素

${ARRAY_NAME[INDEX]}
#如果省略[INDEX]表示引用下标为0的元素

范例:

[root@centos7: ~]#declare -a nums=({1..10})
[root@centos7: ~]#echo ${nums[0]}
1
[root@centos7: ~]#echo ${nums}
1

引用数组所有元素

${ARRAY_NAME[*]}
${ARRAY_NAME[@]}

范例:

[root@centos7: ~]#echo ${#nums[*]}
10

数组的长度,即数组中元素的个数

${#ARRAY_NAME[*]}
${#ARRAY_NAME[@]}

7.6 删除数组

删除数组中的某个元素,会导致稀疏格式

unset ARRAY[INDEX]

范例:

[root@centos7: ~]#echo ${nums[*]}
1 2 3 4 5 6 7 8 9 10
[root@centos7: ~]#unset nums[1]
[root@centos7: ~]#echo ${nums[*]}
1 3 4 5 6 7 8 9 10

删除整个数组

unset ARRAY

范例:

[root@centos7: ~]#unset nums
[root@centos7: ~]#echo ${nums[*]}

[root@centos7: ~]#

7.7 数组数据处理

数组切片:

${ARRAY[@]:offset:number}
offset    #要跳过的元素个数
number	  #要取出的元素个数
#取偏移量之后的所有元素
{ARRAY[@]:offset}

范例:

[root@centos7: ~]#echo ${nums[*]:2:3}
3 4 5
[root@centos7: ~]#echo ${nums[@]:5}
6 7 8 9 10

向数组中追加元素:

ARRAY[${#ARRAY[*]}]=VALUE

范例:

[root@centos7: ~]#echo ${nums[*]}
1 2 3 4 5 6 7 8 9 10
[root@centos7: ~]#nums[${#nums[@]}]=11
[root@centos7: ~]#echo ${nums[*]}
1 2 3 4 5 6 7 8 9 10 11

7.8 关联数组

declare -A ARRAY_NAME
ARRAY_NAME=([idx_name]='val1' [idx_name2]='val2' ...)

注意关联数组必须先声明再调用

范例:

[root@centos7: ~]#name[ceo]=ceo
[root@centos7: ~]#name[cto]=cto
[root@centos7: ~]#echo ${name[ceo]}
cto
[root@centos7: ~]#unset name
[root@centos7: ~]#declare -A name
[root@centos7: ~]#name[ceo]=ceo
[root@centos7: ~]#name[cto]=cto
[root@centos7: ~]#name[coo]=coo
[root@centos7: ~]#echo ${name[*]}
ceo coo cto
[root@centos7: ~]#

7.9 范例

范例:生成10个随机数保存于数组中,并找出其最大值和最小值

#!/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 ${nums[*]}
echo -e "Min is $min \n Max is $max"

范例:编写脚本,定义一个数组,数组中的元素对应的值是/var/log目录下所有以.log结尾的文件;统计出其下标为偶数的文件中的行数之和

#!/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

练习

1.输入若干个数值存入数组中,采用冒泡算法进行升序或降序排序

2.下图所示,实现转置矩阵matrix.sh

1 2 3 1 4 7
4 5 6 =====》 2 5 8
7 8 9 3 6 9

3.打印杨辉三角形

8. 字符串处理

8.1 字符串切片

​ 基于偏移量取子串

#返回字符串变量var的长度
${#var }	

#返回字符串变量var中从第 offset个字符后(不包括第offset个字符)的字符开始,到最后的部分,offset的取值在0到${#var}-1 之间(bash4.2后, 允许为负值)
${var:offset} 

#返回字符串变量var中从第offset个字符后(不包括第offset个字符)的字符开始,长度为number的部分
${var:offset:number}

#取字符串的最右侧几个字符,取字符串的最右侧几个字符,注意:冒号后必须有一空白字符
${var: -1ength}

#从最左侧跳过offset字符,一直向右取到距离最右侧1ength个字符之前的内容
${var:offset:-1ength}

#先从最右侧向左取到length个字符开始,再向右取到距离最右侧offset个字符之间的内容,注意: -1ength前空格
${var: -1ength:-offset}:

​ 基于模式取子串

#其中word可以是指定的任意字符 功能:自左而右,查找var变量所存储的字符串中,第一次出现的word, 删除字符串开头至第一次出现word字符串(含)之间的所有字符
${var#*word}

#同上,贪婪模式,不同的是,删除的是字符串开头至最后一次由word指定的字符之间的所有内容
${var##*word}

​ 范例

[root@centos7: ~]#file="var/log/messages"
[root@centos7: ~]#echo ${file#*/}
log/messages
[root@centos7: ~]#echo ${file##*/}
messages
#其中word可以是指定的任意字符.功能:自右而左,查找var变量所存储的字符串中,第一次出现的word,删除字符串最后一个字符向左至第一次出现word字符串(含)之间的所有字符
${var%word*}
#同上,只不过删除字符串最右侧的字符向左至最后一次出现word字符之间的所有字符
${var%%word*}

​ 范例:

[root@centos7: ~]#file="var/log/messages"
[root@centos7: ~]#echo ${file%/*}
var/log
[root@centos7: ~]#echo ${file%%/*}
var

8.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}

8.3 查找并删除

#删除var表示的字符串中第一次被pattern匹配到的字符串
${var/pattern}

#删除var表示的字符串中所有被pattern匹配到的字符串
${var//pattern}

#删除var表示的字符串中所有以pattern为行首匹配到的字符串
${var/#pattern}

删除var所表示的字符串中所有以pattern为行尾所匹配到的字符串
${var/%pattern}

8.4 字符大小写替换

#:把var中的所有小写字母转换为大写
${var^^}

#把var中的所有大写字母转换为小写
${var,,}

9 高级变量

9.1 高级变量赋值

变量配置方式str没有配置str为空字符串str已配置为非空字符串
var=${str-expr}var=exprvar=var=$str
var=${str:-expr}var=exprvar=exprvar=$str
var=${str+expr}var=var=exprvar=expr
var=${str:+expr}var=var=var=expr
var=${str=expr} str=expr
var=expr
str不变
var=
str不变
var=$str
var=${str:=expr} str=expr
var=expr
str=expr
var=expr
str不变
var=$str
var=${str?expr} expr输出至stderr var= var=$str
var=${str:?expr} expr输出至stderr expr输出至stderr var=$str

范例:

[root@centos7: ~]#title=ceo
[root@centos7: ~]#name=${title-test}
[root@centos7: ~]#echo ${name}
ceo
[root@centos7: ~]#title=
[root@centos7: ~]#name=${title-test}
[root@centos7: ~]#echo ${name}

[root@centos7: ~]#unset title
[root@centos7: ~]#name=${title-test}
[root@centos7: ~]#echo ${name}
test

9.2 高级变量用法-有类型变量

Shell变量一般是无类型的,但是bash Shell提供了declare和typeset两个命令用于指定变量的类型,两个命令是等价的

declare[选项]变量名
-r  声明或显示只读变量
-i  将变量定义为整型数
-a 将变量定义为数组
-A 将变量定义为关联数组
-f   显示已定义的所有函数名及其内容
-F  仅显示已定义的所有函数名
-x   声明或显示环境变量和环境函数
-l   声明变量为小写字母declare -l var=UPPER
-u  声明变量为大写字母declare -u var=lower

9.3 变量间接引用

9.3.1 eval命令

eval命令将会首先扫描命令行进行所有的置换,然后再执行该命令。该命令适用于那些一次扫描无法实现其功能的变量该命令对变量进行两次扫描

范例:

root@centos7: ~]#CMD=whoami
[root@centos7: ~]#echo ${CMD}
whoami
[root@centos7: ~]#eval ${CMD}
root
root@centos7: ~]#${CMD}
root

9.3.2 间接变量引用

如果第一个变量的值是第二个变量的名字,从第一个变引用第二个变量的值就称为间接变引用
variable1的值是variable2,而variable2又 是变量名,variable2的值为value, 间接变量引用是指通过variable1获得变量值value的行为

variable1=variable2
variable2=value

bash Shell提供了两种格式实现间接变量引用

#第一种:转义$
eval tempvar=\$$variable1
#第二种
tempvar=${!variable}

范例:

[root@centos7: ~]#name1=name2
[root@centos7: ~]#name2=变量
[root@centos7: ~]#echo ${name1}
name2
[root@centos7: ~]#echo ${name2}
变量
[root@centos7: ~]#eval tmp=\$$name1
[root@centos7: ~]#echo $tmp
变量

#第二种
[root@centos7: ~]#echo ${!name1}
变量

=


var= s t r < / t d > < / t r > < t r > < t d > v a r = str </td> </tr> <tr> <td>var= str</td></tr><tr><td>var={str:?expr}

expr输出至stderr


expr输出至stderr


var=$str

范例:

[root@centos7: ~]#title=ceo
[root@centos7: ~]#name=${title-test}
[root@centos7: ~]#echo ${name}
ceo
[root@centos7: ~]#title=
[root@centos7: ~]#name=${title-test}
[root@centos7: ~]#echo ${name}

[root@centos7: ~]#unset title
[root@centos7: ~]#name=${title-test}
[root@centos7: ~]#echo ${name}
test

9.2 高级变量用法-有类型变量

Shell变量一般是无类型的,但是bash Shell提供了declare和typeset两个命令用于指定变量的类型,两个命令是等价的

declare[选项]变量名
-r  声明或显示只读变量
-i  将变量定义为整型数
-a 将变量定义为数组
-A 将变量定义为关联数组
-f   显示已定义的所有函数名及其内容
-F  仅显示已定义的所有函数名
-x   声明或显示环境变量和环境函数
-l   声明变量为小写字母declare -l var=UPPER
-u  声明变量为大写字母declare -u var=lower

9.3 变量间接引用

9.3.1 eval命令

eval命令将会首先扫描命令行进行所有的置换,然后再执行该命令。该命令适用于那些一次扫描无法实现其功能的变量该命令对变量进行两次扫描

范例:

root@centos7: ~]#CMD=whoami
[root@centos7: ~]#echo ${CMD}
whoami
[root@centos7: ~]#eval ${CMD}
root
root@centos7: ~]#${CMD}
root

9.3.2 间接变量引用

如果第一个变量的值是第二个变量的名字,从第一个变引用第二个变量的值就称为间接变引用
variable1的值是variable2,而variable2又 是变量名,variable2的值为value, 间接变量引用是指通过variable1获得变量值value的行为

variable1=variable2
variable2=value

bash Shell提供了两种格式实现间接变量引用

#第一种:转义$
eval tempvar=\$$variable1
#第二种
tempvar=${!variable}

范例:

[root@centos7: ~]#name1=name2
[root@centos7: ~]#name2=变量
[root@centos7: ~]#echo ${name1}
name2
[root@centos7: ~]#echo ${name2}
变量
[root@centos7: ~]#eval tmp=\$$name1
[root@centos7: ~]#echo $tmp
变量

#第二种
[root@centos7: ~]#echo ${!name1}
变量
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值