第十五章 SHELL脚本编程进阶

175 阅读11分钟

@[TOC](第十五章 SHELL脚本编程进阶)

实验⼀:建⽴循环脚本

⽬的

建立循环脚本。

前提

linux系统,centos6、centos7或ubuntu,连接网络。

命令介绍

1、循环

1.1、for循环

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

【例1】循环。

[root@localhost data]# for id in a b c d ;do echo id is $id ; done
id is a
id is b
id is c
id is d

[root@localhost data]# for id in {a..d} ;do echo id is $id ; done
id is a
id is b
id is c
id is d

【例2】循环。

[root@localhost data]# for id in {1..10} ;do echo id is $id ; done
id is 1
id is 2
id is 3
id is 4
id is 5
id is 6
id is 7
id is 8
id is 9
id is 10

[root@localhost data]# for id in {1..10 } ;do echo id is $id ; done
id is {1..10
id is }

注意:10后不能有空格。

【例3】循环,递增2。

[root@localhost data]# for id in {1..10..2} ;do echo id is $id ; done
id is 1
id is 3
id is 5
id is 7
id is 9

【例4】循环,递减2。

[root@localhost data]# for id in {10..1..-2} ;do echo id is $id ; done
id is 10
id is 8
id is 6
id is 4
id is 2

【例5】seq。

[root@localhost data]# seq 10
1
2
3
4
5
6
7
8
9
10

【例6】seq,1到10,递增3。

[root@localhost data]# seq 1 3 10
1
4
7
10

【例7】循环+seq。

[root@localhost data]# for id in `seq 1 3 10` ;do echo id is $id ; done
id is 1
id is 4
id is 7
id is 10
[root@localhost data]# for id in *.sh ;do echo id is $id ; done
id is 9x9.sh
id is arg.sh

【例8】循环,批量改⽂件名。

[root@localhost data]# mkdir /data/dir
[root@localhost data]# touch /data/dir/1.jpg
[root@localhost data]# touch /data/dir/2.png
[root@localhost data]# touch /data/dir/3.bmp
[root@localhost data]# ll /data/dir/
total 0
-rw-r--r-- 1 root root 0 May 18 10:06 1.jpg
-rw-r--r-- 1 root root 0 May 18 10:06 2.png
-rw-r--r-- 1 root root 0 May 18 10:06 3.bmp

[root@localhost data]# vim rename.sh 
#!/bin/bash
for file in /data/dir/*;do
	file_name=`echo $file |sed -nr 's#(.*)\..*$#\1#p'`
	mv $file ${file_name}.log
	echo $file is rename ${file_name}.log
done

[root@localhost data]# bash rename.sh 
/data/dir/1.jpg is rename /data/dir/1.log
/data/dir/2.png is rename /data/dir/2.log
/data/dir/3.bmp is rename /data/dir/3.log

【例9】for循环,1+100。

[root@localhost data]# vim sum.sh
#!/bin/bash
sum=0
for i in {1..100};do
let sum+=i
done
echo sum=$sum
[root@localhost data]# bash sum.sh
sum=5050

[root@localhost data]# seq -s+ 100 | bc
5050

[root@localhost data]# echo {1..100} | tr " " "+" | bc
5050

[root@localhost data]# echo {1..100} | tr " \t" "+" | bc
5050

[root@localhost data]# seq 100 | tr "\n" "+" | sed 's#\+$#\n#g' | bc
5050

【例10】批量创建⽤户。

#把用户名写到文件中
[root@localhost data]# cat /data/name.txt 
zhao
qian
sun
li

#脚本调用文件
[root@localhost data]# vim creatuser.sh
#!/bin/bash
for USER in `cat /data/name.txt`;do
	if id $USER &>/dev/null;then
		echo $USER is exist!
	else 
		useradd $USER
		echo $SUER |passwd --stdin $USER &>/dev/null
		echo $USER is created!
	fi
done

[root@localhost data]# bash creatuser.sh
zhao is created!
qian is created!
sun is created!
li is created!

[root@localhost data]# chmod +x creatuser.sh 
[root@localhost data]# ./creatuser.sh 
zhao is exist!
qian is exist!
sun is exist!
li is exist!

【例11】批量创建⽤户,选项。

[root@localhost data]# cat batch_users.sh
#!/bin/bash
if [ "$1" = '-c' ];then
	for USER in `cat /data/name.txt`;do
		if id $USER &> /dev/null;then
			echo "$USER is exist"
		else
			useradd $USER
			echo $USER | passwd --stdin $USER &>/dev/null
			echo "$USER is created"
		fi
	done
elif [ "$1" = '-r' ];then
	for USER in `cat /data/name.txt`;do
		userdel -r $USER &> /dev/null
		echo $USER is remove
	done
else 
	echo "Usage: `basename $0` -c | -r "
fi

[root@localhost data]# bash batch_users.sh -c	#创建
zhao is created
qian is created
sun is created
li is created
[root@localhost data]# bash batch_users.sh -r	#删除
zhao is remove
qian is remove
sun is remove
li is remove

【例12】扫描地址段。

[root@localhost data]# vim scan.sh 
#!/bin/bash
#IP地址、可变更
NET=192.168
for SUBNET in {0..3};do 
	for HOST in {1..5};do
		ping -c1 -W1 $NET.$SUBNET.$HOST &> /dev/null && echo $NET.$SUBNET.$HOST is up! || echo $NET.$SUBNET.$HOST is down!
	done
	wait
done
wait
echo "scan host is finished" 

[root@localhost data]# bash scan.sh 
172.22.0.1 is up!
172.22.0.2 is down!
172.22.0.3 is down!
172.22.0.4 is down!
172.22.0.5 is down!
172.22.1.1 is down!
172.22.1.2 is down!
172.22.1.3 is down!
172.22.1.4 is down!
172.22.1.5 is down!
172.22.2.1 is down!
172.22.2.2 is down!
172.22.2.3 is down!
172.22.2.4 is down!
172.22.2.5 is down!
172.22.3.1 is down!
172.22.3.2 is down!
172.22.3.3 is down!
172.22.3.4 is down!
172.22.3.5 is down!
scan host is finished

【例13】扫描地址段,同时。

[root@localhost data]# vim scan_host.sh 
#!/bin/bash
NET=192.168
for SUBNET in {0..255};do
	{
	for HOST in {1..254};do
	{ ping -c1 -W1 $NET.$SUBNET.$HOST &> /dev/null && echo $NET.$SUBNET.$HOST is up; 
	}&
	done
	}&
	wait
done
wait
echo "scan host is finished"

[root@localhost data]# bash scan_host.sh 
172.22.0.6 is up
172.22.0.1 is up
172.22.0.7 is up
172.22.0.50 is up
172.22.0.100 is up
172.22.0.111 is up
172.22.0.154 is up
...

【例14】星星,随机闪光。

[root@localhost data]# vim star.sh 
#!/bin/bash
read -p "Line: " line
read -p "col: " col
#col=10
#line=20
BEGIN='\033[1;5;'
for i in `seq $line`;do
	for j in `seq $col`;do
		COL=$[RANDOM%7+31]m
		echo -e "${BEGIN}${COL}*\c"
	done
		echo -e '\033[0m'
done

[root@localhost data]# bash star.sh 
Line: 5
col: 5
*****
*****
*****
*****

【例15】100内累加,for循环中,(( ))的⽤法。

[root@localhost data]# vim sum_for.sh 
#!/bin/bash
sum=0
for ((i=1;i<=100;i++));do
	let sum+=i
done
echo sum=$sum

[root@localhost data]# bash sum_for.sh 
sum=5050

【例16】随机数,最⼤,最⼩。

[root@localhost data]# vim max_min_for.sh 
#!/bin/bash
for((i=0;i<10;i++));do
	N=$RANDOM
	echo -e "$N \c"
	if [ $i -eq 0 ];then
	MAX=$N
	MIN=$N
	else
#if [ $N -gt $MAX ];then
# MAX=$N
#fi
#if [ $N -lt $MIN ];then
# MIN=$N
#fi
	[ $N -gt $MAX ] && MAX=$N
	[ $N -lt $MIN ] && MIN=$N
	fi
done
echo 
echo MAX=$MAX,MIN=$MIN


[root@localhost data]# bash max_min_for.sh 
27556 5595 925 26685 9777 6638 31368 12717 23468 2834 
MAX=31368,MIN=925

1.2、while循环

while CONDITION; do
	循环体
done

【例17】100内累加.

[root@localhost data]# vim sum_while.sh 
#!/bin/bash
sum=0
i=1
while [ $i -le 100 ];do
	let sum+=i
	let i++
done
echo sum=$sum

[root@localhost data]# bash sum_while.sh 
sum=5050

【例18】国际象棋,棋盘。

[root@localhost data]# vim chess1_while.sh
#!/bin/bash
i=1
BEGIN='\033[1;'
RED=41m
YELLOW=43m
while [ $i -le 8 ] ;do
        j=1
        while [ $j -le 4 ];do
                if [ $[i%2] -eq 0 ];then
                        echo -e "${BEGIN}${RED} ${BEGIN}${YELLOW} \c"
                else
                        echo -e "${BEGIN}${YELLOW} ${BEGIN}${RED} \c"
                fi
                let j++
        done
        echo -e "\e[0m"
        let i++ 
done
[root@localhost data]# cat chess2_while.sh
#!/bin/bash
i=1
BEGIN='\033[1;'
RED=41m
YELLOW=43m
while [ $i -le 8 ] ;do
	if [ $[i%2] -eq 0 ];then
		j=1
		while [ $j -le 4 ];do
		echo -e "${BEGIN}${RED} ${BEGIN}${YELLOW} \c"
		let j++
		done
	else
		j=1
		while [ $j -le 4 ];do
		echo -e "${BEGIN}${YELLOW} ${BEGIN}${RED} \c"
		let j++
		done
	fi
	echo -e "\e[0m"
	let i++
done
[root@localhost data]# cat chess3_while.sh
#!/bin/bash
i=1
BEGIN='\033[1;'
RED=41m
YELLOW=43m
flag=true
while [ $i -le 8 ] ;do
	if $flag ;then
		j=1
	while [ $j -le 4 ];do
		echo -e "${BEGIN}${RED} ${BEGIN}${YELLOW} \c"
		let j++
	done
		flag=false
	else
		j=1
		while [ $j -le 4 ];do
		echo -e "${BEGIN}${YELLOW} ${BEGIN}${RED} \c"
		let j++
	done
		flag=true
	fi
		echo -e "\e[0m"
		let i++
done

【例19】监控httpd服务。

#安装、启动httpd服务
[root@localhost data]# yum install httpd -y
[root@localhost data]# systemctl start httpd
[root@localhost data]# pidof httpd
2968 2967 2966 2965 2964 2963

[root@localhost data]# vim monitor_httpd_while.sh 
#!/bin/bash
SLEEPTIME=30
SERVICE=httpd
LOG=/var/log/monitor_$SERVICE.log
while true;do
	if killall -0 $SERVICE &>/dev/null;then
	true
	else
	systemctl restart $SERVICE
	echo "AT `date +'%F %T'` $SERVICE is restart" | tee -a $LOG | mail -s warning root
	fi
	sleep $SLEEPTIME
done

[root@localhost ~]# bash monitor_httpd_while.sh

#打开workspace2,或者用xshell,再打开同一个主机。杀死进程,查看日志。
[root@localhost ~]# killall httpd
[root@localhost ~]# tail -f /var/log/monitor_httpd.log 
AT 2019-05-20 09:47:12 httpd is restart
AT 2019-05-20 09:47:46 httpd is restart
AT 2019-05-20 09:48:46 httpd is restart

1.3、until循环

until CONDITION; do
	循环体
done

1.3.1、循环控制语句continue⽤于循环体中

#continue [N]:提前结束第N层的本轮循环,而直接进入下一轮判断;最内层为,第1层
while CONDTIITON1; do
	CMD1
	...
	if CONDITION2; then
	continue
	fi
	CMDn
	...
done

【例20】continue.

[root@localhost data]# vim continue.sh 
#!/bin/bash
for ((i=0;i<10;i++));do
        if [ $i -eq 5 ];then continue;fi
        echo i=$i
done

[root@localhost data]# bash continue.sh 
i=0
i=1
i=2
i=3
i=4
i=6
i=7
i=8
i=9

【例21】continue嵌套,循环第⼆层。

[root@localhost data]# vim continue2.sh 
#!/bin/bash
for ((i=0;i<3;i++));do
        for ((j=0;j<10;j++));do
                if [ $j -eq 5 ];then continue 2;fi
                echo j=$j
        done
                echo i=$i
done

[root@localhost data]# bash continue2.sh 
j=0
j=1
j=2
j=3
j=4
j=0
j=1
j=2
j=3
j=4
j=0
j=1
j=2
j=3
j=4

1.3.2、循环控制语句break

#用于循环体中break [N]:提前结束第N层循环,最内层为第1层
while CONDTIITON1; do
	CMD1
	...
	if CONDITION2; then
	break
	fi
	CMDn
	...
done

【例22】break

[root@localhost data]# vim break.sh 
#!/bin/bash
for ((i=0;i<10;i++));do
        if [ $i -eq 5 ];then break ;fi
        echo i=$i
done

[root@localhost data]# bash break.sh 
i=0
i=1
i=2
i=3
i=4

【例23】break嵌套,循环第⼆层。

[root@localhost data]# vim break2.sh 
#!/bin/bash
for ((i=0;i<3;i++));do
        for ((j=0;j<10;j++));do
                if [ $j -eq 5 ];then break 2;fi
                echo j=$j
        done
        echo i=$i
done

[root@localhost data]# bash break2.sh 
j=0
j=1
j=2
j=3
j=4

1.3.2、循环控制shift命令

shift [n]
用于将参量列表 list 左移指定次数,缺省为左移一次。
参量列表 list 一旦被移动,最左端的那个参数就从列表中删除。while 循环遍历位置参量列表时,常用到
shift
./doit.sh a b c d e f g h
./shfit.sh a b c d e f g h

【例24】shift,增加⽤户&删除⽤户。

[root@localhost data]# vim createuser_shift.sh
#!/bin/bash
if [ -z "$1" ];then
	echo "Usage: `basename $0` username ..."
	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

[root@localhost data]# bash createuser_shift.sh a b c
a is created	#创建
b is created
c is created

[root@localhost data]# bash createuser_shift.sh a b c
a is exist		#存在
b is exist
c is exist


[root@localhost data]# getent passwd 查看用户
root:x:0:0:root:/root:/bin/bash
... 中间省略
apache:x:48:48:Apache:/usr/share/httpd:/sbin/nologin
a:x:1005:1005::/home/a:/bin/bash
b:x:1006:1006::/home/b:/bin/bash
c:x:1007:1007::/home/c:/bin/bash

#删除用户(a,b,c)
[root@localhost data]# for i in {a..c};do userdel -r $i;done

[root@localhost data]# getent passwd 查看用户
root:x:0:0:root:/root:/bin/bash
... 中间省略
apache:x:48:48:Apache:/usr/share/httpd:/sbin/nologin

【例25】读取⽂件,逐⾏处理。

[root@localhost data]# vim name.txt 
zhao
qian
sun
li

[root@localhost data]# vim while_read1.sh 
#!/bin/bash
while read name;do
	useradd $name
	echo $name is created
done < name.txt

[root@localhost data]# bash while_read1.sh 
zhao is created
qian is created
sun is created
li is created

#第二种形式:
[root@localhost data]# vim while_read2.sh 
#!/bin/bash
cat name.txt | while read name;do
	useradd $name
	echo $name is created
done

[root@localhost data]# bash while_read2.sh 
useradd: user 'zhao' already exists
zhao is created
useradd: user 'qian' already exists
qian is created
useradd: user 'sun' already exists
sun is created
useradd: user 'li' already exists
li is created

删除用户:
[root@localhost data]# for i in `cat name.txt`;do userdel -r $i;done

【例26】程序执⾏结果,逐⾏处理。每分钟检查磁盘利⽤率,超过10%的报警。

[root@localhost data]# vim disk_check_while.sh 
#!/bin/bash 
df | while read line ;do
	DEVICE=`echo $line | sed -rn '/^\/dev\/sd/s@^([^ ]+).* ([0-9]+)%.*$@\1@p'`
	USED=`echo $line | sed -rn '/^\/dev\/sd/s@^([^ ]+).* ([0-9]+)%.*$@\2@p'`
	if [ $USED -gt 10 ];then
		wall $DEVICE will be full,USED:$USED%
	fi
done

[root@localhost data]# chmod +x disk_check_while.sh 
[root@localhost data]# pwd
/data
[root@localhost data]# crontab -e
* * * * * /data/disk_check_while.sh


[root@localhost data]# 
Broadcast message from root@localhost.localdomain (Mon May 20 11:29:01 2019):

/dev/sda1 will be full,USED:16%

Broadcast message from root@localhost.localdomain (Mon May 20 11:30:01 2019):

/dev/sda1 will be full,USED:16%
^C
You have new mail in /var/spool/mail/root

【例27】⼤于10次的,丢⼊防⽕墙,拒绝访问。ss.log文件下载

[root@localhost data]# vim deny_while.sh 
#!/bin/bashsed -nr '/^ESTAB/s#.* ([0-9.]+):[0-9]+.*$#\1#p' ss.log|sort |uniq -c |while read times ip
;do
        if [ $times -gt 10 ];then
                iptables -A INPUT -s $ip -j REJECT
        fi
done

[root@localhost data]# iptables -F
[root@localhost data]# iptables -vnL
Chain INPUT (policy ACCEPT 22 packets, 1452 bytes)
 pkts bytes target     prot opt in     out     source               destination         

Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         

Chain OUTPUT (policy ACCEPT 12 packets, 1136 bytes)
 pkts bytes target     prot opt in     out     source               destination

[root@localhost data]# rz
导入ss.log文件

[root@localhost data]# bash deny_while.sh 
[root@localhost data]# iptables -vnL
Chain INPUT (policy ACCEPT 6 packets, 396 bytes)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 REJECT     all  --  *      *       127.0.0.1            0.0.0.0/0            reject-with icmp-port-unreachable

Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         

Chain OUTPUT (policy ACCEPT 4 packets, 448 bytes)
 pkts bytes target     prot opt in     out     source               destination

【例28】分析错误⽇志

[root@localhost data]# rz
导入hack.log文件
[root@localhost data]# lastb -f hack.log |sed '/^$/d;$d'|sed -rn 's#.* ([0-9.]{7,15})
.*#\1#p' |sort |uniq -c |sort -nr|head
 86294 58.218.92.37
 43148 58.218.92.26
 18036 112.85.42.201
 10501 111.26.195.101
 10501 111.231.235.49
 10501 111.204.186.207
 10501 111.11.29.199
 10499 118.26.23.225
 6288 42.7.26.142
 4236 58.218.92.30

1.4、select循环与菜单

select variable in list 
do 
	循环体命令
done

【例29】点菜。

[root@localhost data]# vim menu_select.sh 
#!/bin/bash
PS3="please choose a number: "
select menu in suancaiyu kaoquanyang yangxiezi huoguo quit;do 
	case $menu in
	suancaiyu)
		echo $menu price is26
		echo your input is $REPLY
		;;
	kaoquanyang)
		echo $menu price is600
		echo your input is $REPLY
		;;
	yangxiezi)
		echo $menu price is150
		echo your input is $REPLY
		;;
	huoguo)
		echo $menu price is100
		echo your input is $REPLY
		;;
	quit)
		break
		;;
	*)
		echo your input is $RELPY
		echo input false
	esac
done
echo "点菜成功"

[root@localhost data]# bash menu_select.sh 
1) suancaiyu
2) kaoquanyang
3) yangxiezi
4) huoguo
5) quit
please choose a number: 2
kaoquanyang price is600
your input is 2
please choose a number: 5
点菜成功

1.5、定义函数

f_name ()
{
	...函数体...
}

删除shell函数
命令格式为:unset function_name

使子进程也可使用
声明:export -f function_name
查看:export -f 或 declare -xf

【例30】定义函数

[root@localhost data]# function func1 () {
> ls
> hostname
> }
[root@localhost data]# func1
9x9.sh 					continue.sh 			hack.log 		set.sh
arg.sh 					correct.txt 			hello.sh 		shengdanshu.sh
backup-script 			createuser_shift.sh 	history.sh 		son.sh
backup-script2019-05-14 creatuser.sh issue.out 	ss.log
localhost.localdomain

【例31】调⽤函数

[root@localhost data]# vim func1.sh 
#!/bin/bash
func1(){
 echo func1
}
func2(){
 echo func2
}
hostname

[root@localhost data]# bash func2.sh 
localhost.localdomain
func1

【例32】定义函数,脚本。

[root@localhost data]# vim functions
#!/bin/bash
func1(){
	echo func1
	}
func2(){
	echo func2
	}

[root@localhost data]# cat func2.sh 
#!/bin/bash
source functions
func1

[root@localhost data]# bash func2.sh 
func1

【例33】批量创建⽤户[要在文件夹下有name.txt文件]

[root@localhost data]# vim batch_users.sh 
#!/bin/bash
. /etc/init.d/functions
if [ "$1" = '-c' ];then
	for USER in `cat /data/name.txt`;do
		if id $USER &> /dev/null;then
			action "$USER is exist" false
		else
			useradd $USER
			echo $USER | passwd --stdin $USER &>/dev/null
			action "$USER is create" true
		fi
	done
elif [ "$1" = '-r' ];then
	for USER in `cat /data/name.txt`;do
		userdel -r $USER &> /dev/null
		echo $USER is removed
	done
else 
	echo "Usage: `basename $0` -c | -r "
fi

[root@localhost data]# bash batch_users.sh -r
zhao is removed
qian is removed
sun is removed
li is removed
[root@localhost data]# bash batch_users.sh -c
zhao is create                                            [  OK  ]
qian is create                                            [  OK  ]
sun is create                                             [  OK  ]
li is create                                              [  OK  ]

【例34】return,返回值。

[root@localhost data]# vim functions
#!/bin/bash
func1(){
	local name=mage
	echo func1:$name
	return
	echo func1
	}
func2(){
	echo func2
	}

[root@localhost data]# vim func2.sh 
#!/bin/bash
source functions
name=wang
echo func3.sh:$name
func1
echo return:$?
echo func2.sh:$name

[root@localhost data]# bash func2.sh 
func3.sh:wang
func1:mage
return:0
func2.sh:wang

【例35】定义函数,⽤时调⽤。

[root@localhost data]# vim functions
#!/bin/bash

func1(){
	local name=mage
	echo func1:$name
	return 10
	echo func1
}
func2(){
	echo 1st arg is $1
	echo 2st arg is $2
	echo all args are $*
	echo all args are $@
	echo the number is $#
	echo func2
}
is_digit(){
	while [ "$1" ];do
		[[ ! "$1" =~ ^[0-9]+$ ]] && { echo input not a digit;return 1; } 
		shift
	done
	return 0 
}
compare() {
	is_digit $*
	[ $? -eq 1 ] && return
	if [ "$1" -gt "$2" ];then
		echo $1
	else
		echo $2
	fi
}
fact(){
	if [ $1 -eq 1 ];then
		echo 1
	else
		echo $[`fact $[$1-1]`*$1]
	fi
}

[root@localhost data]# vim func3.sh 
#!/bin/bash
. functions
func2 a b c

[root@localhost data]# bash func3.sh 
1st arg is a
2st arg is b
all args are a b c
all args are a b c
the number is 3
func2

【例36】定义函数,⽤时调⽤。

[root@localhost data]# vim functions
#!/bin/bash
func1(){
	local name=mage
	echo func1:$name
	return 10
	echo func1
}

func2(){
	echo 1st arg is $1
	echo 2st arg is $2
	echo all args are $*
	echo all args are $@
	echo the number is $#
	echo func2
}

is_digit(){
	while [ "$1" ];do
		[[ ! "$1" =~ ^[0-9]+$ ]] && { echo input not a digit;return 1; } 
	shift
	done
	return 0 
}

compare() {
	is_digit $*
	[ $? -eq 1 ] && return
	if [ "$1" -gt "$2" ];then
		echo $1
	else
		echo $2
	fi
}

fact(){
	if [ $1 -eq 1 ];then
		echo 1
	else
		echo $[`fact $[$1-1]`*$1]
	fi
}

[root@localhost data]# vim func4.sh 
#!/bin/bash
. functions
compare $1 $2

[root@localhost data]# bash func4.sh 10 20
20

1.6、函数递归:

函数直接或间接调用自身 注意递归层数

【例37】阶乘

[root@localhost data]# vim functions
#!/bin/bash
func1(){
	local name=mage
	echo func1:$name
	return 10
	echo func1
}
func2(){
	echo 1st arg is $1
	echo 2st arg is $2
	echo all args are $*
	echo all args are $@
	echo the number is $#
	echo func2
}
is_digit(){
	while [ "$1" ];do
		[[ ! "$1" =~ ^[0-9]+$ ]] && { echo input not a digit;return 1; } 
		shift
	done
		return 0 
}
compare() {
	is_digit $*
	[ $? -eq 1 ] && return
		if [ "$1" -gt "$2" ];then
			echo $1
		else
			echo $2
		fi
}
fact(){
	if [ $1 -eq 1 ];then
		echo 1
	else
		echo $[`fact $[$1-1]`*$1]
	fi
}
[root@localhost data]# vim fact.sh 
#!/bin/bash
. functions
fact $1
[root@localhost data]# bash fact.sh 4
24
[root@localhost data]# bash fact.sh 8
40320

【例38】⾃⼰调⽤⾃⼰,死循环。

[root@localhost data]# vim f1.sh 
#!/bin/bash
echo $$
sleep 1
./f1.sh
[root@localhost data]# chmod +x f1.sh
[root@localhost data]# bash f1.sh 
9741
9743
9745
9747
^C

【例39】国际象棋,棋盘

[root@localhost data]# vim chess6.sh 
#!/bin/bash
red(){
	echo -e "\033[41m \033[0m\c"
}
yel(){
	echo -e "\033[43m \033[0m\c" 
}
redyel(){
	for ((i=1;i<=4;i++));do
		for ((j=1;j<=4;j++));do
			red;yel
	done
			echo
done
}
yelred(){
	for ((i=1;i<=4;i++));do
		for ((j=1;j<=4;j++));do
			yel;red
	done
			echo 
done
}
	for ((line=1;line<=8;line++));do
		[ $[$line%2] -eq 0 ] && redyel || yelred
done
[root@localhost data]# vim chess7.sh 
#!/bin/bash
red(){
	echo -e "\033[41m \033[0m\c"
}
yel(){
	echo -e "\033[43m \033[0m\c" 
}
redyel(){
	for ((i=1;i<=4;i++));do
	for ((j=1;j<=4;j++));do
		[ "$1" = "-r" ] && { yel;red; } || { red;yel; }
	done
		echo
done
}
	for ((line=1;line<=8;line++));do
		[ $[$line%2] -eq 0 ] && redyel || redyel -r
done

1.7、信号捕捉trap

trap '触发指令' 信号
 	进程收到系统发出的指定信号后,将执行自定义指令,而不会执行原操作
trap '' 信号
 	忽略信号的操作
trap '-' 信号
 	恢复原信号的操作
trap -p
 	列出自定义信号操作
trap finish EXIT 
 	当脚本退出时,执行finish函数

【例40】补捉,Ctrl + c:不退出。

[root@localhost data]# kill -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			

ctrl + c = 信号2

终止sleep的方法:
[root@localhost data]# sleep 1000
^C (Ctrl + c)
[root@localhost data]# sleep 1000
 (用xshell打开相同的主机,发送信号:killall -2 sleep)
[root@localhost data]# vim trap.sh 
#!/bin/bash
trap 'echo press ctrl+c' int (或者写成:2for i in {1..10};do
	echo i=$i
	sleep 1
done

[root@localhost data]# bash trap.sh 
i=1
i=2
i=3
^Cpress ctrl+c
i=4
i=5
^Cpress ctrl+c
i=6
i=7
i=8
i=9
^Cpress ctrl+c
i=10

【例41】补捉信号,忽略,恢复,终⽌。

[root@localhost data]# cat trap.sh 
#!/bin/bash
finish(){
	echo finish run
}
trap 'echo press ctrl+c' int 
trap -p
for i in {1..10};do
	echo i=$i
	sleep 1
done
trap '' int 
trap -p
for i in {11..20};do
	echo i=$i
	sleep 1
done
trap '-' int
trap finish EXIT
trap -p
#20-30之间Ctrl+c可以退出
for i in {21..30};do
	echo i=$i
	sleep 1
done

[root@localhost data]# bash trap.sh 
trap -- 'echo press ctrl+c' SIGINT
i=1
i=2
^Cpress ctrl+c
i=3
i=4
i=5
i=6
^Cpress ctrl+c
i=7
i=8
i=9
i=10
trap -- '' SIGINT
i=11
^Ci=12
i=13
i=14
i=15
^Ci=16
i=17
i=18
i=19
i=20
trap -- 'finish' EXIT
i=21
^Cfinish run

1.8、数组

变量:存储单个元素的内存空间
数组:存储多个元素的连续的内存空间,相当于多个变量的集合
数组名和索引
	索引:编号从0开始,属于数值索引
	注意:索引可支持使用自定义的格式,而不仅是数值格式,即为关联索引,bash4.0版本之后开始支持
	bash的数组支持稀疏格式(索引不连续)
声明数组:
	declare -a ARRAY_NAME
	declare -A ARRAY_NAME 关联数组
	注意:两者不可相互转换

【例42】数组,赋值。

[root@localhost data]# title[0]=ceo
[root@localhost data]# title[1]=coo
[root@localhost data]# title[2]=cfo
[root@localhost data]# echo ${title[0]}
ceo
[root@localhost data]# echo ${title[1]}
coo
[root@localhost data]# echo ${title[2]}
cfo

【例43】数组,赋值。

[root@localhost data]# read -a name
zhao qian sun li
[root@localhost data]# echo ${name[0]}
zhao
[root@localhost data]# echo ${name[1]}
qian
[root@localhost data]# echo ${name[2]}
sun
[root@localhost data]# echo ${name[3]}
li

【例44】数组,赋值。

[root@localhost data]# title=([0]=ceo [1]=coo [2]=cfo)
[root@localhost data]# echo ${title[0]}
ceo
[root@localhost data]# echo ${title[1]}
coo
[root@localhost data]# echo ${title[2]}
cfo
[root@localhost data]# echo ${title[*]}
ceo coo cfo
[root@localhost data]# echo ${title[@]}
ceo coo cfo
[root@localhost data]# echo ${#title[@]} 	#显示个数
3
[root@localhost data]# echo ${title[*]} 	#显示全部
ceo coo cfo
[root@localhost data]# echo ${title[*]:0:1} 	#显示从0开始的第1个
ceo
[root@localhost data]# echo ${title[*]:1:2} 	#显示从开始的2个
coo cfo
[root@localhost data]# title[${#title[@]}]=ofo #添加为新的数值
[root@localhost data]# echo ${title[*]}
ceo coo cfo ofo

【例45】数组,赋值。

[root@localhost data]# goods=(car plane ship)
[root@localhost data]# echo ${goods[0]}
car
[root@localhost data]# echo ${goods[1]}
plane
[root@localhost data]# echo ${goods[2]}
ship

【例46】数组,赋值。

[root@localhost data]# alpha=({a..z})
[root@localhost data]# echo ${alpha[0]}
a
[root@localhost data]# echo ${alpha[25]}
z

【例47】数组,赋值。

[root@localhost data]# file=(*.sh) 		#赋值所有[*]以[.sh]结尾文件
[root@localhost data]# echo ${file[*]}		#打印所有[*]以[.sh]结尾文件
9x9.sh arg.sh backup.sh batch_users.sh break2.sh break.sh busybox.sh chess1_while.sh
chess2_while.sh chess3_while.sh chess6.sh chess7.sh chook_rabbit.sh continue2.sh
continue.sh createuser_shift.sh creatuser.sh deny_while.sh disk_check_while.sh elsfk.sh
exit.sh f1.sh fact.sh father.sh func1.sh func22.sh func2.sh func3.sh func4.sh hello.sh
history.sh max_min_for.sh menu_select.sh menu.sh monitor_httpd_while.sh rename.sh
scan_host.sh scan.sh score.sh sendfile.sh set.sh shengdanshu.sh son.sh star.sh sum_for.sh
sum.sh sum_while.sh systeminfo.sh trap2.sh trap.sh triangleTree30.sh while_read1.sh
while_read2.sh xing.sh yesorno.sh
[root@localhost data]# echo ${file[1]}		#打印第二个
arg.sh
[root@localhost data]# echo ${file[10]}
chess6.sh

【例48】关联数组,必须先声明才能使⽤。

[root@localhost data]# student[a]='aaa'
[root@localhost data]# student[b]='bbb'
[root@localhost data]# student[c]='ccc'
[root@localhost data]# echo ${student[a]}
ccc
[root@localhost data]# echo ${student[b]}
ccc
[root@localhost data]# echo ${student[c]}
ccc
[root@localhost data]# unset student 		#删除标准数组
[root@localhost data]# declare -A student 		#声明关联数组
[root@localhost data]# student[a]='aaa'
[root@localhost data]# student[b]='bbb'
[root@localhost data]# student[c]='ccc'
[root@localhost data]# echo ${student[a]}
aaa
[root@localhost data]# echo ${student[b]}
bbb
[root@localhost data]# echo ${student[c]}
ccc

【例49】声明。

[root@localhost data]# cat array.sh 
#!/bin/bash
declare -a ARR
for((i=0;i<10;i++));do
	ARR[$i]=$RANDOM
	if [ $i -eq 0 ];then
		MAX=${ARR[$i]}
		MIN=${ARR[$i]}
	else
		[ ${ARR[$i]} -gt $MAX ] && MAX=${ARR[$i]}
		[ ${ARR[$i]} -lt $MIN ] && MIN=${ARR[$i]}
	fi
done
echo all random are ${ARR[@]}
echo max=$MAX, min=$MIN
[root@localhost data]# bash array.sh 
all random are 10879 4743 27808 32368 7586 3182 10149 18273 22520 26331
max=32368, min=3182

1.9、字符串切⽚

 ${#var}:返回字符串变量var的长度
 ${var:offset}:返回字符串变量var中从第offset个字符后(不包括第offset个字符)的字符开始,到最后的
部分,offset的取值在0 到 ${#var}-1 之间(bash4.2后,允许为负值)
 ${var:offset:number}:返回字符串变量var中从第offset个字符后(不包括第offset个字符)的字符开始,
长度为number的部分
 ${var: -length}:取字符串的最右侧几个字符
注意:冒号后必须有一空白字符
 ${var:offset:-length}:从最左侧跳过offset字符,一直向右取到距离最右侧lengh个字符之前的内容
 ${var: -length:-offset}:先从最右侧向左取到length个字符开始,再向右取到距离最右侧offset个字符之
间的内容

【例50】字符串切⽚

[root@localhost data]# 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
[root@localhost data]# echo {a..z}|tr -d ' '
abcdefghijklmnopqrstuvwxyz
[root@localhost data]# str=`echo {a..z}|tr -d ' '`
[root@localhost data]# echo ${#str}
26
[root@localhost data]# echo ${str:3} 从第三个开始
defghijklmnopqrstuvwxyz
[root@localhost data]# echo ${str:3:4} 从第三个开始的4个
defg
[root@localhost data]# echo ${str: -3} 倒数3个
xyz
[root@localhost data]# echo ${str::4} 显示前四个
abcd
[root@localhost data]# echo ${str::4 -3} 从第四个开始倒数三个
a

1.10、字符串处理

基于模式取子串
	${var#*word}:其中word可以是指定的任意字符
 	功能:自左而右,查找var变量所存储的字符串中,第一次出现的word, 删除字符串开头至第一次出现word字符串(含)之间的所有字符
	${var##*word}:同上,贪婪模式,不同的是,删除的是字符串开头至最后一次由word指定的字符之间的所有内容
${var%word*}:其中word可以是指定的任意字符
	功能:自右而左,查找var变量所存储的字符串中,第一次出现的word, 删除字符串最后一个字符向左至第一次出现word字符串(含)之间的所有字符
	file="/var/log/messages"
	${file%/*}: /var/log
${var%%word*}:同上,只不过删除字符串最右侧的字符向左至最后一次出现word字符之间的所有字符

查找替换
${var/pattern/substr}:查找var所表示的字符串中,第一次被pattern所匹配到的字符串,以substr替换之
${var//pattern/substr}: 查找var所表示的字符串中,所有能被pattern所匹配到的字符串,以substr替换之
${var/#pattern/substr}:查找var所表示的字符串中,行首被pattern所匹配到的字符串,以substr替换之
${var/%pattern/substr}:查找var所表示的字符串中,行尾被pattern所匹配到的字符串,以substr替换之

查找并删除

${var/pattern}:删除var表示的字符串中第一次被pattern匹配到的字符串
${var//pattern}:删除var表示的字符串中所有被pattern匹配到的字符串
${var/#pattern}:删除var表示的字符串中所有以pattern为行首匹配到的字符串
${var/%pattern}:删除var所表示的字符串中所有以pattern为行尾所匹配到的字符串

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

【例51】字符串处理,##贪婪模式。

[root@localhost data]# getent passwd root
root:x:0:0:root:/root:/bin/bash
[root@localhost data]# line=`getent passwd root`
[root@localhost data]# echo $line
root:x:0:0:root:/root:/bin/bash
[root@localhost data]# echo ${line#*root}
:x:0:0:root:/root:/bin/bash
[root@localhost data]# echo ${line##*root}
:/bin/bash
[root@localhost data]# echo ${line%root*}
root:x:0:0:root:/
[root@localhost data]# echo ${line%%root*}
 							(删除所有)

【例52】字符串处理,取值。

[root@localhost data]# url=http://www.magedu.com:80
[root@localhost data]# echo ${url##*:}
80
[root@localhost data]# echo ${url%%:*}
http

1.11、Shell变量⼀般是⽆类型的,但是bash Shell提供了declare和typeset两个命令⽤于指定变量的类型,两个命令是等价的

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

【例53】声明变量,⼤⼩写问题。

[root@localhost data]# declare -l aaa=HHH
[root@localhost data]# echo $aaa
hhh
[root@localhost data]# declare -u bbb=ggg
[root@localhost data]# echo $bbb
GGG

1.12、eval命令

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

【例54】eval命令

[root@localhost data]# n=10
[root@localhost data]# for i in {1..$n};do echo $i;done
{1..10}
[root@localhost data]# echo {1..$n}
{1..10}
[root@localhost data]# eval echo {1..$n}
1 2 3 4 5 6 7 8 9 10
[root@localhost data]# for i in `eval echo {1..$n}`;do echo $i;done
1
2
3
4
5
6
7
8
9
10

【例55】eval命令

[root@localhost data]# title=ceo
[root@localhost data]# ceo=mage
[root@localhost data]# eval echo $title
ceo
[root@localhost data]# eval echo $$title
7699title
[root@localhost data]# eval ${!title}
bash: mage: command not found...
[root@localhost data]# echo ${!title}
mage

1.13、expect命令

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

选项
 -c:从命令行执行expect脚本,默认expect是交互地执行的
 示例:expect -c 'expect "\n" {send "pressed enter\n"}
 -d:可以输出输出调试信息
 示例:expect -d ssh.exp

expect中相关命令

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

【例56】安装expect命令。

复制文件到另一台主机,交互式方式,需要输入yes和密码不方便。
[root@localhost data]# scp /etc/issue root@192.168.23.128:/data
The authenticity of host '192.168.23.128 (192.168.23.128)' can't be established.
ECDSA key fingerprint is SHA256:CpbUHLVMaIFixU/wVQODmdwYdlSkklNWagUjIMlpRPI.
ECDSA key fingerprint is MD5:13:3f:da:8e:dd:ec:cd:25:32:b0:04:36:97:2b:d6:0c.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '192.168.23.128' (ECDSA) to the list of known hosts.
root@192.168.23.128's password: 
issue 100% 35 10.3KB/s 00:00 
[root@localhost data]# yum install expect -y 安装
...
Running transaction
 Installing : 1:tcl-8.5.13-8.el7.x86_64 1/2 
 Installing : expect-5.45-14.el7_1.x86_64 2/2 
 Verifying : 1:tcl-8.5.13-8.el7.x86_64 1/2 
 Verifying : expect-5.45-14.el7_1.x86_64 2/2 
Installed:
 expect.x86_64 0:5.45-14.el7_1 
Dependency Installed:
 tcl.x86_64 1:8.5.13-8.el7 
Complete!

【例57】expect命令,⾮交互式命令,远程复制。

[root@localhost data]# vim expect1
#!/bin/expect
spawn scp /etc/fstab root@192.168.23.128:/data
expect {
"yes/no" { send "yes\n";exp_continue }
"password" { send "123456\n" }
}
expect eof

[root@localhost data]# chmod +x expect1

[root@localhost data]# ./expect1
spawn scp /etc/fstab root@192.168.23.128:/data
root@192.168.23.128's password: 
fstab                                                            100%  633   363.9KB/s   00:00    

【例58】expect命令,⾮交互式命令,远程删除。

[root@localhost data]# vim expect2
#!/bin/expect
spawn ssh 192.168.23.128 rm -f /data/fstab
expect {
"yes/no" { send "yes\n";exp_continue }
"password" { send "123456\n" }
}
expect eof

[root@localhost data]# chmod +x expect2

[root@localhost data]# ./expect2
spawn ssh 192.168.23.128 rm -f /data/fstab
root@192.168.23.128's password: 

【例59】expect命令,⾮交互式命令,远程登录。

[root@localhost data]# vim expect3
#!/usr/bin/expect
#IP地址
set ip 192.168.23.128
#用户名
set user root
#密码
set password 123456
set timeout 10
spawn ssh $user@$ip
expect {
"yes/no" { send "yes\n";exp_continue }
"password" { send "$password\n" }
}
interact
[root@localhost data]# chmod +x expect3
[root@localhost data]# ./expect3
spawn ssh root@192.168.23.128
root@192.168.23.128's password: 
Last login: Mon May 20 17:22:31 2019 from 192.168.23.1


退出,远程登录的主机:
[root@localhost data]# exit
logout
Connection to 192.168.23.128 closed.

【例60】expect命令,⾮交互式命令,以变量⽅式远程登录。

[root@localhost data]# vim expect4
#!/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@localhost data]# chmod +x expect4

[root@localhost data]# ./expect4 192.168.23.128 root 123456
spawn ssh root@192.168.23.128
root@192.168.23.128's password: 
Last failed login: Mon May 20 17:46:15 CST 2019 from 192.168.23.118 on ssh:notty
There was 1 failed login attempt since the last successful login.
Last login: Mon May 20 17:41:05 2019 from 192.168.23.118

[root@localhost ~]# exit
logout
Connection to 192.168.23.128 closed.

【例61】expect命令,⾮交互式命令,以变量⽅式远程登录,添加⽤户。

[root@localhost data]# vim expect5
#!/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 magedu |passwd --stdin haha\n" }
send "exit\n"
expect eof

[root@localhost data]# chmod +x expect5

[root@localhost data]# ./expect5 192.168.23.128 root 123456
spawn ssh root@192.168.23.128
root@192.168.23.128's password: 
Last login: Mon May 20 17:46:26 2019 from 192.168.23.118
[root@localhost ~]# useradd haha
[root@localhost ~]# echo magedu |passwd --stdin haha
Changing password for user haha.
passwd: all authentication tokens updated successfully.
[root@localhost ~]# exit
logout
Connection to 192.168.23.128 closed.

【例62】以shell脚本模式,执⾏expect命令,⾮交互式命令,以变量⽅式远程登录,添加⽤户。

[root@localhost data]# vim expect6.sh 
#!/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 hehe\n" }
expect "]#" { send "echo magedu |passwd --stdin hehe\n" }
expect "]#" { send "exit\n" }
expect eof
EOF

[root@localhost data]# chmod +x expect6.sh

[root@localhost data]# ./expect6.sh 192.168.23.128 root 123456
spawn ssh root@192.168.23.128
root@192.168.23.128's password: 
Last login: Mon May 20 17:52:13 2019 from 192.168.23.118
[root@localhost ~]# useradd hehe
useradd: user 'hehe' already exists
[root@localhost ~]# echo magedu |passwd --stdin hehe
Changing password for user hehe.
passwd: all authentication tokens updated successfully.
[root@localhost ~]# exit
logout
Connection to 192.168.23.128 closed.