shell编程进阶

循环

常见的循环的命令:for, while, until
格式一:
for 变量名 in 列表;do
循环体
done
格式二:
for ((控制变量初始化;条件判断表达式;控制变量的修正表达式))
do
循环体
done
范例:
1 打印99乘法表

[root@centos8|8|scripts]#bash 99.sh 
1*1=1	
1*2=2	2*2=4	
1*3=3	2*3=6	3*3=9	
1*4=4	2*4=8	3*4=12	4*4=16	
1*5=5	2*5=10	3*5=15	4*5=20	5*5=25	
1*6=6	2*6=12	3*6=18	4*6=24	5*6=30	6*6=36	
1*7=7	2*7=14	3*7=21	4*7=28	5*7=35	6*7=42	7*7=49	
1*8=8	2*8=16	3*8=24	4*8=32	5*8=40	6*8=48	7*8=56	8*8=64	
1*9=9	2*9=18	3*9=27	4*9=36	5*9=45	6*9=54	7*9=63	8*9=72	9*9=81	
1*10=10	2*10=20	3*10=30	4*10=40	5*10=50	6*10=60	7*10=70	8*10=80	9*10=90	10*10=100	
[root@centos8|9|scripts]#cat 99.sh 
#/bin/bash

for i in {1..10} ;do
	for j in `seq $i`;do
		echo -e "${j}*${i}=$[i*j]\t\c"
	done
	echo
done
[root@centos8|11|scripts]#bash 992.sh 
1x1=1	
2x1=2	2x2=4	
3x1=3	3x2=6	3x3=9	
4x1=4	4x2=8	4x3=12	4x4=16	
5x1=5	5x2=10	5x3=15	5x4=20	5x5=25	
6x1=6	6x2=12	6x3=18	6x4=24	6x5=30	6x6=36	
7x1=7	7x2=14	7x3=21	7x4=28	7x5=35	7x6=42	7x7=49	
8x1=8	8x2=16	8x3=24	8x4=32	8x5=40	8x6=48	8x7=56	8x8=64	
9x1=9	9x2=18	9x3=27	9x4=36	9x5=45	9x6=54	9x7=63	9x8=72	9x9=81	
[root@centos8|12|scripts]#cat 992.sh
#/bin/bash
for ((i=1;i<=9;i++));do
	for((j=1;j<=i;j++));do
		echo -e "${i}x${j}=$[i*j]\t\c"
	done
		echo 
done

2 把data/test下的文件改为以bak结尾

[root@centos8|45|test]#ls
10.log  1.log  2.log  3.log  4.log  5.log  6.log  7.log  8.log  9.log
10.mp   1.mp   2.mp   3.mp   4.mp   5.mp   6.mp   7.mp   8.mp   9.mp
10.txt  1.txt  2.txt  3.txt  4.txt  5.txt  6.txt  7.txt  8.txt  9.txt
[root@centos8|50|scripts]#bash for_rename.bak 
[root@centos8|52|scripts]#ls /data/test/
10.bak  1.bak  2.bak  3.bak  4.bak  5.bak  6.bak  7.bak  8.bak  9.bak
[root@centos8|53|scripts]#cat for_rename.bak 
#/bin/bash
DIR=/data/test
cd $DIR
	for FILE in *;do
	PRE=`echo $FILE |sed -nr 's/(.*)\.([^.]+)$/\1/p' `
	mv $FILE $PRE.bak
done

3 要求将目录YYYY-MM-DD/中所有文件,移动到YYYY-MM/DD/下


#1 yyyy-mm-dd10.sh 创建YYYY-MM-DD,当前日期一年前365天到目前共365个目录,里面有10个文
件,$RANDOM.log
[root@centos8|82|data]#cat scripts/YYMMDD.sh 
#bin/bash
for i in {1..365};do
	DIR=`date -d "-$i day" +%F`
	mkdir $DIR
	cd $DIR
for n in {1..10};do
	touch $RANDOM.log
done
	cd ..
done
2 移动到YYYY-MM/DD/下
[root@centos8|91|data]#cat 123.sh 
#bin/bash
DIR=/data
cd $DIR
for DIR2 in * ;do
	PDIR=`echo $DIR2 | cut -d"-" -f1,2`
	SUBDIR=`echo $DIR2 | cut -d"-" -f3`
	[ -d $PDIR/$SUBDIR ] || mkdir -p $PDIR/$SUBDIR $> /dev/null
	mv $DIR2/*  $PDIR/$SUBDIR
done

```bash
[root@centos8|97|data]#bash 111.sh
5050
[root@centos8|98|data]#cat 111.sh 
#/bin/bash
sum=0
for ((i=1;i<=100;i++));
do
	let sum+=i
done
echo $sum

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

[root@centos8|108|data]#cat 333.sh
#/bin/bash
cd /var
for FILE in * ;
do
	if [ -f $FILE ];
	then 
		echo "$FILE 是普通文件";
	elif [ -d $FILE ];
	then
		echo "$FILE 是目录文件";
	elif [ -h $FILE ];
	then
	        echo "$FILE 是链接文件";
	else 
		echo "为其他文件";
	fi
done

2、添加10个用户user1-user10,密码为8位随机字符

[root@centos8|116|data]#cat user.sh
#bin/bash



USER=`echo user{1..10}`
for NAME in $USER ;do
	useradd $NAME
	PASS=`cat /dev/urandom | tr -dc '[[:alnum:]]' | head -c8`
	echo $PASS | passwd --stdin &> /dev/null
	passwd -e $NAME &> /dev/null
	echo $NAME:$PASS >> /root/user.txt
	echo $NAME 已经创建好
done

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

[root@centos8|133|data]#cat 44.sh
#/bin/bash


cd /etc/rc.d/rc3.d
for FILE  in * ;
do
    [[ "$FILE" =~ ^K. ]]&& echo "$FILE stop"
    [[ "$FILE" =~ ^S. ]]&& echo "$FILE start"
done

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

[root@centos8|145|data]#cat 6.sh
#/bin/bash

read -p "请输入一个数字" NUM
sum=0
NUMBER=`seq $NUM`
for i in $NUMBER ;do
	let sum+=$i;

done
echo $sum

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

[root@centos8|148|data]#bash 2.sh
1683
[root@centos8|149|data]#cat 2.sh
#/bin/bash
sum=0
for i in {3..100..3};do
	let sum+=$i
done
echo $sum

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

[root@centos8|158|data]#bash 4.sh
请输入一个ip地址www.baidu.com
在线
[root@centos8|159|data]#cat 4.sh
#/bin/bash
read -p "请输入一个ip地址" IP
ping -c1 -W1 $IP &> /dev/null && echo "在线" || echo "不在线" 

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

[root@centos8|166|data]#bash 8.sh 
文件已经创建好
[root@centos8|167|data]#ls /testdir/
10.html  1.html  2.html  3.html  4.html  5.html  6.html  7.html  8.html  9.html
[root@centos8|168|data]#cat 8.sh 
#/bin/bash
cd /testdir
RAM=`cat /dev/urandom | tr -dc [[:alpha:]] | head -c8`
for (( i=1;i<=10;i++ ));do
	touch ${i}${RAN}.html
done
echo "文件已经创建好"

9、打印等腰三角形

[root@centos8|211|data]#bash 232.sh 
        *
       ***
      *****
     *******
    *********
   ***********
  *************
 ***************
*****************
[root@centos8|212|data]#cat 232.sh
#!/bin/bash

for ((i=1;i<=9;i++))
do
	for((j=9;j>i;j--))
do
	echo -n " "
done
	for ((j=2;j<=i;j++))
do
	echo -n "*"
done
	for ((j=1;j<=i;j++))
do
	echo -n "*"
done
	echo
done

while循环

格式:
while CONDITION ;do
循环体
done
范例:
编写脚本,求100以内所有正奇数之和sum=0

[root@centos8|219|data]#cat 100.sh
#/bin/bash
sum=0
i=1
while [ $i -le 100 ];do
	sum=$[$sum+$i]
	i=$[$i+2]
done
echo "$sum"
[root@centos8|220|data]#bash 100.sh 
2500

当硬盘使用率超过80%时给用户发邮件

[root@centos8|223|data]#cat 000.sh
#/bin/bash
WARNING=80
while :;do
       USE=`df | sed -nr '/^\/dev\/sda/s/^([^ ]+) .* ([0-9]+)%.*/\1 \2/p'|sort -nr | head -1`
if [ $USE -gt $WARNING ];then
 	echo DISK will be full from `hostname -I` | mail -s "disk warning" [email protected]
fi
sleep 10
done

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

#/bin/bash
read -p "请输入IP地址"IP
NET=echo $IP | cut -d. -f1,2
for ((i=1;i<=254;i++));do
	for((j=1;j<=254;j++));do
	ping -c1 -W1 $NET.$j.$i &> /dev/null && { echo $net.$i is up;echo $net.$i >> host.log; }
done
done

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

[root@centos8|229|data]#bash 99.sh
1x1=1	
2x1=2	2x2=4	
3x1=3	3x2=6	3x3=9	
4x1=4	4x2=8	4x3=12	4x4=16	
5x1=5	5x2=10	5x3=15	5x4=20	5x5=25	
6x1=6	6x2=12	6x3=18	6x4=24	6x5=30	6x6=36	
7x1=7	7x2=14	7x3=21	7x4=28	7x5=35	7x6=42	7x7=49	
8x1=8	8x2=16	8x3=24	8x4=32	8x5=40	8x6=48	8x7=56	8x8=64	
9x1=9	9x2=18	9x3=27	9x4=36	9x5=45	9x6=54	9x7=63	9x8=72	9x9=81	
[root@centos8|230|data]#cat 99.sh
#/bin/bash
i=1
while [ $i -le 9 ];do
	j=1
	while [ $j -le $i ];do
		echo -e "${i}x${j}=$[i*j]\t\c"
		let j++
	done
	echo 
	let i++
done

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

[root@centos8|254|data]#bash pp.sh 
2012

3674

30088

26708

28625

2133

6763

3231

10789

30088 2012
[root@centos8|255|data]#cat pp.sh
#/bin/bash
i=1
while [ $i -lt 10 ];do
	NUM=$RANDOM
	if [ $i -eq 1 ];then
		MAX=$NUM
		MIN=$NUM
	else
		if [ $MAX -lt $NUM ];then
			MAX=$NUM
		elif [ $MIN -gt $NUM ];then
			MIN=$NUM
		else
			true
		fi
	fi
	let i++
echo -e "$NUM\n"	
done
echo "$MAX $MIN"

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

[root@centos7|119|data]#cat chess1.sh
#!/bin/bash

for i in {1..8};do
        temp1=$[ $i % 2 ]

        for j in {1..8};do
        temp2=$[ $j % 2 ]

        if [ $temp1 -eq  $temp2  ];then
                echo -e -n "\033[47m  \033[0m"
        else
                echo -e -n "\033[41m  \033[0m"
        fi

        done

        echo 
done

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

RAN=1 
cat test.txt | while read CHESS;do 
  { while true;do 
    MD=echo $RAN|md5sum|cut -c1-10 
    if [[ "$MD" == "$CHESS" ]];then 
      echo $RAN 
      break 
    else 
      let RAN++ 
    fi 
  done }& 
  wait 
done

无限循环

while true; do
循环体
done

until循环

格式:

until CONDITION; do  
循环体  
done  
说明:  
进入条件: CONDITION 为false  
退出条件: CONDITION 为true 

无限循环

until false; do  
循环体  
Done 

continue循环

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

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

break循环

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

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

范例:点餐系统

[root@centos7|14|data]#cat menu.sh
sum=0
COLOR='echo -e \033[1;31m'
END="\033[0m"
while true;do
cat <<EOF
1) 鲍鱼
2) 燕窝
3) 龙虾
4) 满汉全席
5) 帝王蟹
6) 退出
EOF
read -p "请点餐" MENU
case $MENU in
1|2)
	$COLOR The price is ¥100$END
	let sum+=100

	;;
3|5)
	echo The price is ¥200
	let sum+=200
	;;
4)
	echo The price is ¥10000
	let sum+=10000
	;;
6)
echo "总价是$sum"
	break 
	;;
*)
	echo "点错了"
	;;
esac
echo "总价是$sum"
done

猜数游戏:

[root@centos8|11|data]#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

循环控制shift命令
shift [n] 用于将参量列表 list 左移指定次数,缺省为左移一次。
参量列表 list 一旦被移动,最左端的那个参数就从列表中删除。while 循环遍历位置参量列表时,常用到 shift

[root@centos7|40|data]#cat shift.sh
while [ "$1" ];do
	echo $1
	shift
done
echo shift is finish

一次创建用户

[root@centos8|17|data]#cat shift.sh 
#/bin/bash


if [ $# -eq 0 ];then
	echo "Usage: `basename $0` user1 user2... "
	exit
fi

while [ "$1" ];do
	if id $1 &> /dev/null;then
	echo "$1 已经存在"
else
	useradd $1
	echo "$1 已经创建好"
fi
	shift
done
echo "所有用户已经创建好"

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

[hacker@centos8|15|data]$cat who.sh 
#/bin/bash
while :;do
	if who | grep "^hacker\>" &> /dev/null;then
		who | grep "^hacker\>" &> /dev/null > /data/login.log
		break
	fi
	sleep 3
done

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

[root@centos8|52|data]#cat t.sh 
#/bin/bash
sum=0
while [ $# -gt 0 ];do
	lines=`cat $1|wc -l`
	shift
	sum=$[$sum+$lines]
done
echo $sum

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

[root@centos8|57|data]#bash n.sh 23 33 44 11 99 213 
最大值为 213
最小值为 11
[root@centos8|58|data]#cat n.sh 
#/bin/bash
max=$1
min=$2
while [ $# -gt 0 ];do
	if [ $1 -lt $min ];then
		min=$1
	fi
	if [ $1 -gt $max ];then
		max=$1
	fi
	shift
done
echo "最大值为 $max"
echo "最小值为 $min"

while 特殊用法

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

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

说明:依次读取/PATH/FROM/SOMEFILE文件中的每一行,且将行赋值给变量line
范例:监控硬盘使用率超过80%就给用户发邮件

[root@centos8|80|data]#cat while.sh
#!/bin/bash
WARNING=80
MAIL=123456.qq.com
df | sed -nr '/^\/dev\/sd/s/^([^ ]+) .* ([0-9]+)%.*/\1 \2/p'|while read DEVICE USE;do
	if [ $USE -gt $WARNING ];then
		echo "$DEVICE whill be full use; $USE" | mail -s "DISK WARNING" $MAIL
		fi
done

输出/etc/passwd中bash为/nologin的用户名和UID

[root@centos8|85|data]#cat 9999.sh
#!/bin/bash
while read line ;do
	if [[ "$line" =~ /sbin/nologin$ ]] ;then
	echo $line | cut -d: -f1,3
	fi
done < /etc/passwd

select循环与菜单

格式:

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

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

[root@centos8|91|data]#cat menu.sh
#!/bin/bash
sum=0
PS3="请点菜(1-6): "
select MENU in 北京烤鸭 佛跳墙 小龙虾 羊蝎子 点菜结束;do
case $REPLY in
1)
	echo $MENU 价格是 100
	let sum+=100
	;;
2)
	echo $MENU 价格是 88
	let sum+=88
	;;
3)
	echo $MENU 价格是 66
	let sum+=66
	;;
4)
	echo $MENU 价格是 200
	let sum+=200
	;;
5)
	echo $MENU 价格是 166
	let sum+=166
	;;
6)
	echo "点菜结束,退出"
	break
	;;
*)
	echo "点菜错误,重新选择"
	;;
esac
done
echo "总价格是:$sum"

函数介绍

函数function是由若干条shell命令组成的语句块,实现代码重用和模块化编程
它与shell程序形式上是相似的,不同的是它不是一个单独的进程,不能独立运行,而是shell程序的一部分
函数和shell程序比较相似,区别在于Shell程序在子Shell中运行,而Shell函数在当前Shell中运行。因此在当前Shell中,函数可对shell中变量
进行修改

定义函数:

#语法一:
func_name (){
 ...函数体...
}#语法二:
function func_name {
 ...函数体...
} 
#语法三:
function func_name () {
 ...函数体...
}

查看函数

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

删除函数

unset func_name

函数调用

函数的调用方式
可在交互式环境下定义函数
可将函数放在脚本文件中作为它的一部分
可放在只包含函数的单独文件中
调用:函数只有被调用才会执行,通过给定函数名调用函数,函数名出现的地方,会被自动替换为函数
代码
函数的生命周期:被调用时创建,返回时终止

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

[root@centos8 ~]#dir() {
> ls -l
> }
[root@centos8 ~]#dir
total 4
-rw-------. 1 root root 1559 Nov 7 19:33 anaconda-ks.cfg  

使用函数文件

可以将经常使用的函数存入一个单独的函数文件,然后将函数文件载入shell,再进行调用函数
文件名可任意选取,但最好与相关任务有某种联系,例如:functions
一旦函数文件载入shell,就可以在命令行或脚本中调用函数。可以使用delcare -f 或set 命令查看所有定义的函数,其输出列表包括已经载入shell的所有函数
若要改动函数,首先用unset命令从shell中删除函数。改动完毕后,再重新载入此文件
实现函数文件的过程:

  1. 创建函数文件,只存放函数的定义
  2. 在shell脚本或交互式shell中调用函数文件,格式如下:
. filename 或 source   filename

[root@centos8 ~]#cat functions
#!/bin/bash
#functions
hello(){
    echo Run hello Function
}

hello2(){
    echo Run hello2 Function
}
[root@centos8 ~]#. functions
[root@centos8 ~]#hello
Run hello Function
[root@centos8 ~]#hello2
Run hello2 Function
[root@centos8 ~]#declare -f hello hello2
hello () 
{ 
    echo Run hello Function
}
hello2 () 
{ 
    echo Run hello2 Function
}

系统初始化脚本


. /etc/init.d/functions
disable_selinux(){
 sed -i.bak 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config
 action "SElinux已禁用,重新启动后才可生效" }
disable_firewall(){
 systemctl disable --now firewalld &> /dev/null
 action "防火墙已禁用" }
set_ps1() {
 echo "PS1='\[\e[1;35m\][\u@\h \W]\\$\[\e[0m\]'" > /etc/profile.d/reset.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="请选择相应的编号(1-6): "
MENU='
禁用SELinux
关防火墙
修改提示符

修改网卡名
以上全实现
退出
'
select M in $MENU ;do
case $REPLY in
1)
 disable_selinux
 ;;
2)
 disable_firewall
 ;;
3)
 set_ps1
 ;;
4) 
 set_eth
 ;;
5) 
   disable_selinux
 disable_firewall
 set_ps1
 set_eth
 ;;
6)
 break
 ;;
*)
 echo "请输入正确的数字"
esac
done

函数返回值

函数的执行结果返回值:
使用echo等命令进行输出
函数体中调用命令的输出结果
函数的退出状态码:
默认取决于函数中执行的最后一条命令的退出状态码
自定义退出状态码,其格式为:
return 从函数中返回,用最后状态命令决定返回值
return 0 无错误返回
return 1-255 有错误返回

环境函数

类拟于环境变量,也可以定义环境函数,使子进程也可使用父进程定义的函数
定义环境函数:
export -f function_name 
declare -xf function_name
查看环境函数:
export -f 
declare -xf

函数参数

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

函数变量

变量作用域:
普通变量:只在当前shell进程有效,为执行脚本会启动专用子shell进程;因此,本地变量的作用
范围是当前shell脚本程序文件,包括脚本中的函数
环境变量:当前shell和子shell有效
本地变量:函数的生命周期;函数结束时变量被自动销毁
注意:
如果函数中定义了普通变量,且名称和局部变量相同,则使用本地变量
由于普通变量和局部变量会冲突,建议在函数中只使用本地变量
在函数中定义本地变量的方法
local NAME=VALUE

函数递归

函数递归:函数直接或间接调用自身
求一个数的阶乘

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

练习:
练习

  1. 编写函数,实现OS的版本判断
[root@centos8|86|data]#cat OS.sh
#!/bin/bash
os(){
OS=`cat /etc/redhat-release | sed -nr 's/([[:alpha:] ]+)([0-9]+)\..*/\2/p'`	
case $OS in
6)
  echo "当前操作系统为Centos6"
  	;;
7)
  echo "当前操作系统为Centos7"
  	;;
8)
  echo "当前操作系统为Centos8"
  	;;
*)
  echo "其他版本"
        ;;	
esac
}
os
  1. 编写函数,实现取出当前系统eth0的IP地址
eth0ip () { 
  ip=ifconfig eth0|grep netmask|tr -s ' '|cut -d ' ' -f3 
  echo ip=$ip 
} 
eth0ip
  1. 编写函数,实现打印绿色OK和红色FAILED
[root@centos8|20|~]$cat 00.sh
#!/bin/bash
redgreen(){
	echo -e "\033[1;32mOK\033[0m"
	echo -e "\033[1;31mFAILD\033[0m"

}
redgreen
  1. 编写函数,实现判断是否无位置参数,如无参数,提示错误
args(){ 
  if [ $# -eq 0 ];then 
    echo "please give a parameter!" 
  fi 
} 
args $1
  1. 编写函数,实现两个数字做为参数,返回最大值
[root@centos8|28|~]$cat 777.sh 
#!/bin/bash
read -p "请输入两个数字:" NUM1 NUM2
max(){
	if [ $NUM1 -gt $NUM2 ];then
		echo "MAX is $NUM1"
	else
		echo "MAX is $NUM2"
	fi
}
max
  1. 编写服务脚本/root/bin/testsrv.sh,完成如下要求
    (1) 脚本可接受参数:start, stop, 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
    running…”,如果/var/lock/subsys/SCRIPT_NAME文件不存在,则显示“SCRIPT_NAME is
    stopped…”
    (7)在所有模式下禁止启动该服务,可用chkconfig 和 service命令管理
    说明:SCRIPT_NAME为当前脚本名
  2. 编写脚本/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-linux-x86-
    64.so.2 ==> /mnt/sysroot/lib64/ld-linux-x86-64.so.2
    (5)每次复制完成一个命令后,不要退出,而是提示用户键入新的要复制的命令,并重复完成上述
    功能;直到用户输入quit退出

其他脚本相关工具

信号捕捉 trap

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

范例:

[root@centos8|45|script]#cat 1.sh
#!/bin/bash
trap 'echo "Press ctrl+c"' int quit
trap -p
for ((i=0;i<=10;i++))
do
		sleep 1
		echo $i
done
trap '' int
trap -p
for ((i=11;i<=20;i++))
do
		sleep 1
		echo $i
done
trap '-' int
trap -p
for ((i=21;i<=30;i++))
do
	sleep 1
	echo $i
done
[root@centos8|48|script]#cat 2.sh
#!/bin/bash
finish(){
	echo finish| tee -a /root/finish.log
}

trap finish exit

while :;do
	echo running
	sleep 1
done

创建临时文件夹mktemp

mktemp 命令用于创建并显示临时文件,可避免冲突
常见选项:
-d 创建临时目录
-p DIR或–tmpdir=DIR 指明临时文件所存放目录位置

mktemp /tmp/testXXX
tmpdir=`mktemp –d /tmp/testdirXXX`
mktemp --tmpdir=/testdir testXXXXXX

安装复制文件 install

install命令格式:

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

选项:
-m MODE,默认755
-o OWNER
-g GROUP
范例:

[root@centos7|216|data]#install -m 755 -o lin -g lin aa.sh bb.sh

交互式转化批处理工具expect

Linux 下 expect 脚本语言中交互处理常用命令
expect的执行
chmod 755 expect #加权限
./expect 执行

1. #!/usr/bin/expect
告诉操作系统脚本里的代码使用那一个 shell 来执行。这里的 expect 其实和 Linux 下的 bash、windows 下的 cmd 是一类东西。
注意:这一行需要在脚本的第一行,从而告知操作系统采用 expect 作为 shell 执行脚本。
注意:当使用 #!/usr/bin/expect -d 时,expect 脚本将运行在调试模式,届时脚本执行的全过程将被展示出来。
2. set timeout
设置超时时间,计时单位是:秒,timeout -1 为永不超时。
例如:set timeout 30 为设置超时时间为 30 秒。则当某个 expect 判断未能成功匹配的 30 秒后,将跳过该 expect 判断,执行后续内容。
3.spawn 
它主要的功能是给运行进程加个壳,用来传递交互指令。
spawn 是进入 expect 环境后才可以执行的 expect 内部命令,如果没有装 expect 或者直接在默认的 shell 下执行是找不到 spawn 命令的。所以不要用 “which spawn“ 之类的命令去找 spawn 命令。好比在 windows 里的 dir 就是一个内部命令,这个命令由 shell 自带,你无法找到一个 dir.com 或 dir.exe 的可执行文件。
例如:spawn ssh -l username 192.168.1.1 将为 ssh -l username 192.168.1.1 加壳,届时该命令的交互指令将可以被处理。
4.expect
这里的 expectexpect 的一个内部命令,需要在 expect 环境中执行。该命令用于判断交互中上次输出的结果里是否包含某些字符串,如果有则立即返回。否则如果有设置超时时间,则等待超时时长后返回。
例如:expect "password:" 为判别交互输出中是否包含 "password:" 字符串。
5.send
该命令用于执行交互动作,与手工输入动作等效。
注意: 命令字符串结尾别忘记加上 "\r"(换行符),如果出现异常等待的状态可以核查一下。
例如:send "ispass\r" 为交互中输入 "is pass\r"。
6.interact
执行完成后保持交互状态,把控制权交给控制台,这个时候便可以手工操作。如果没有该命令,命令完成后即退出。
7.$argv 参数数组
expect 脚本可以接受从 bash 传递过来的参数。
其中通过 [lindex $argv n] 可以获得第 n 个参数的值,通过 [lrange $argv a b] 可以获取 a-b 的参数值。
例如:编写 test.sh 脚本,内容如下。
#!/usr/bin/expect
set timeout 2 
set username [lindex $argv 0] 
set password [lindex $argv 1] 
set hostname [lindex $argv 2] 
spawn /usr/bin/ssh $username@$hostname
expect {
"yes/no"
{send "yes\r"; exp_continue;}
"Password:"
{send "$password\r";}
}
expect eof
则通过调用脚本 ./test.sh oracle password 192.168.87.1 可以使用 oracle 用户以密码 password 登录 192.168.87.1,脚本最后自动登出。
8.exp_continue
exp_continue 附加于某个 expect 判断项之后,可以使该项被匹配后,还能继续匹配该 expect 判断语句内的其他项。exp_continue 类似于控制语句中的 continue 语句。
例如:下例将判断交互输出中是否存在 yes/no 或 *assword。如果匹配 yes/no 则输出 yes 并再次执行判断;如果匹配 *assword 则输出 123abc 并结束该段 expect 语句。
expect {
    \"yes/no\" {send \"yes\r\"; exp_continue;}
    \"*assword\" {set timeout 300; send \"123abc\r\";}
}
注意:exp_continue [-continue_timer] 默认情况下 exp_continue 会重高超时时钟,-continue_timer 选项会阻止时钟重新计数(连续计数)。

常见选项:
-c:从命令行执行expect脚本,默认expect是交互地执行的
-d:可以输出输出调试信息
示例:

expect -c 'expect "\n" {send "pressed enter\n"}'
expect  -d ssh.exp

expect中相关命令:
spawn 启动新的进程
expect 从进程接收字符串
send 用于向进程发送字符串
interact 允许用户交互
exp_continue 匹配多个字符串在执行动作后加此命令
expect最常用的语法(tcl语言:模式-动作)
单一分支模式语法:

[root@centos8 test]#expect
expect1.1> expect "hi" {send "You said hi\n"}
hahahixixi
You said hi

范例2:

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

范例3:expect变量

#!/usr/bin/expect
set ip 10.0.0.0
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

范例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

范例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 magedu |passwd --stdin haha\n" }
send "exit\n"
expect eof 

范例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 hehe\n" }
expect "]#" { send "echo magedu |passwd --stdin hehe\n" }
expect "]#" { send "exit\n" }
expect eof
EOF

范例7: shell脚本利用循环调用expect在CentOS和Ubuntu上批量创建用户

NET=10.0.0
user=root
password=magedu
for ID in 6 7 111;do
ip=$NET.$ID
expect <<EOF
set timeout 20
spawn ssh $user@$ip
expect {
        "yes/no" { send "yes\n";exp_continue }
        "password" { send "$password\n" }
}
expect "#" { send "useradd test\n" }
expect "#" { send "exit\n" }
expect eof
EOF
done

数组

#普通数组可以不事先声明,直接使用
declare -a ARRAY_NAME
#关联数组必须先声明,再使用
declare -A ARRAY_NAME
注意:两者不可相互转换

数组赋值

1,一次赋值一个

[root@I|105|~]#weekends[0]="Sunday"
[root@I|106|~]#weekends[4]="Sunday"

2, 一次赋值全部元素
ARRAY_NAME=(“VAL1” “VAL2” “VAL3” …)
范例:

title=("ceo" "coo" "cto")
num=({0..10})
alpha=({a..g})
file=( *.sh )

3 只赋值特定元素
ARRAY_NAME=([0]=“VAL1” [3]=“VAL2” …)

4 交互式数组值对赋值
read -a ARRAY

范例:

[root@centos8 ~]#declare -A course
[root@centos8 ~]#declare -a course
-bash: declare: course: cannot convert associative to indexed array
[root@centos8 ~]#file=( *.sh )
[root@centos8 ~]#declare -A file
-bash: declare: file: cannot convert indexed to associative array

5 显示所有数组
declare -a

引用数组

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

[root@centos8 ~]#declare -a title=([0]="ceo" [1]="coo" [2]="cto")
[root@centos8 ~]#echo ${title[1]}
coo
[root@centos8 ~]#echo ${title}
ceo
[root@centos8 ~]#echo ${title[2]}
cto
[root@centos8 ~]#echo ${title[3]}

引用数组所有元素
${ARRAY_NAME[*]}
${ARRAY_NAME[@]}
范例:

[root@centos8 ~]#echo ${title[@]}
ceo coo cto
[root@centos8 ~]#echo ${title[*]}
ceo coo cto

数组长度

[root@centos8 ~]#echo ${#title[*]}
3

删除数组:
unset ARRAY[INDEX]

[root@centos8 ~]#echo ${title[*]}
ceo coo cto
[root@centos8 ~]#unset title[1]
[root@centos8 ~]#echo ${title[*]}
ceo cto

删除整个数组:
unset ARRAY

数组数据处理

数组切片

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

范例:

[root@centos8 ~]#num=({0..10})
[root@centos8 ~]#echo ${num[*]:2:3}
2 3 4
[root@centos8 ~]#echo ${num[*]:6}
6 7 8 9 10

数组中追加元素

[root@centos8 ~]#num[${#num[@]}]=11
[root@centos8 ~]#echo ${#num[@]}
12
[root@centos8 ~]#echo ${num[@]}
0 1 2 3 4 5 6 7 8 9 10 11

关联数组

declare -A ARRAY_NAME
ARRAY_NAME=([idx_name1]=‘val1’ [idx_name2]='val2‘…)
范例:

[root@centos8 ~]#name[ceo]=mage
[root@centos8 ~]#name[cto]=wang
[root@centos8 ~]#name[coo]=zhang
[root@centos8 ~]#echo ${name[ceo]}
zhang
[root@centos8 ~]#echo ${name[cto]}
zhang
[root@centos8 ~]#echo ${name[coo]}
zhang
[root@centos8 ~]#echo ${name}
zhang
[root@centos8 ~]#declare -A name
-bash: declare: name: cannot convert indexed to associative array
[root@centos8 ~]#unset name
[root@centos8 ~]#declare -A name
[root@centos8 ~]#name[ceo]=mage
[root@centos8 ~]#name[cto]=wang
[root@centos8 ~]#name[coo]=zhang
[root@centos8 ~]#echo ${name[coo]}
zhang
[root@centos8 ~]#echo ${name[ceo]}
mage
[root@centos8 ~]#echo ${name[cto]}
wang
[root@centos8 ~]#echo ${name[*]}
mage wang zhang

范例:
生成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 "All numbers are ${nums[*]}"
echo Max is $max
echo Min is $min

范例:编写脚本,定义一个数组,数组中的元素对应的值是/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"

字符串处理

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

基于模式取子串

#其中word可以是指定的任意字符,自左而右,查找var变量所存储的字符串中,第一次出现的word, 删除
字符串开头至第一次出现word字符串(含)之间的所有字符
${var#*word}: #同上,贪婪模式,不同的是,删除的是字符串开头至最后一次由word指定的字符之间的所有内容
${var##*word}:

范例:

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

范例:

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

查找替换

#查找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,,}

变量间接引用
eval

[root@I|146|~]#CMD=hostname
[root@I|147|~]#echo $CMD
hostname
[root@I|148|~]#eval $CMD
centos7
[root@I|149|~]#n=10
[root@I|150|~]#echo {0..$n}
{0..10}
[root@I|151|~]#eval echo {0..$n}
0 1 2 3 4 5 6 7 8 9 10

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

bash Shell提供了两种格式实现间接变量引用
eval tempvar=\$$variable1
tempvar=${!variable1}
`
```bash
#其中word可以是指定的任意字符,功能:自右而左,查找var变量所存储的字符串中,第一次出现的word, 
删除字符串最后一个字符向左至第一次出现word字符串(含)之间的所有字符
${var%word*}
#同上,只不过删除字符串最右侧的字符向左至最后一次出现word字符之间的所有字符
${var%%word*}

范例:

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

查找替换

#查找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,,}

变量间接引用
eval

[root@I|146|~]#CMD=hostname
[root@I|147|~]#echo $CMD
hostname
[root@I|148|~]#eval $CMD
centos7
[root@I|149|~]#n=10
[root@I|150|~]#echo {0..$n}
{0..10}
[root@I|151|~]#eval echo {0..$n}
0 1 2 3 4 5 6 7 8 9 10

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

bash Shell提供了两种格式实现间接变量引用
eval tempvar=\$$variable1
tempvar=${!variable1}

邮件配置

邮件配置
[root@centos7|7|~]#cat .mailrc
set from=邮箱地址
set smtp=smtp.qq.com
set smtp-auth-user=邮箱地址
set smtp-auth-password=授权码
set smtp-auth=login
set ssl-verify=ignore

你可能感兴趣的:(shell,linux)