Linux进阶_shell脚本编程之进阶篇

成功不易,加倍努力!

      • 1 循环
        • 1.1 循环执行介绍
        • 1.2 for循环
        • 1.3 while循环
        • 1.4 until循环
        • 1.5 循环控制语句 continue
        • 1.6 循环控制语句 break
        • 1.7 循环控制 shift 命令
        • 1.8 while read 特殊用法
        • 1.9 select 循环与菜单
      • 2 函数介绍
        • 2.1 管理函数
          • 2.1.1 定义函数
          • 2.1.2 查看函数
          • 2.1.3 删除函数
        • 2.2 函数调用
          • 2.2.1 交互式环境调用函数
          • 2.2.2 在脚本中定义及使用函数
          • 2.2.3 使用函数文件
        • 2.3 函数返回值
        • 2.4 环境函数
        • 2.5 函数参数
        • 2.6 函数变量
        • 2.7 函数递归
      • 3 其它脚本相关工具
        • 3.1 信号捕捉 trap
        • 3.2 创建临时文件 mktemp
        • 3.3 安装复制文件 install
        • 3.4 交互式转化批处理工具 expect
      • 4 数组array
        • 4.1 数组介绍
        • 4.2 声明数组
        • 4.3 数组赋值
        • 4.4 显示所有数组
        • 4.5 引用数组
        • 4.6 删除数组
        • 4.7 数组数据处理
        • 4.8 关联数组
        • 4.9 范例
      • 5 字符串处理
        • 5.1 字符串切片
        • 5.2 查找替换
        • 5.3 查找并删除
        • 5.4 字符大小写转换
      • 6 高级变量
        • 6.1 高级变量赋值
        • 6.2 高级变量用法-有类型变量
        • 6.3 变量间接引用
          • 6.3.1 eval命令
          • 6.3.2 间接变量引用
          • 6.3.3 变量引用reference
      • shell脚本编程之基础篇

1 循环

1.1 循环执行介绍

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

重复运行次数

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

常见的循环的命令:for, while, until
Linux进阶_shell脚本编程之进阶篇_第1张图片

1.2 for循环

[root@centos7 ~]#help for
for: for NAME [in WORDS ... ] ; do COMMANDS; done
	Execute commands for each member in a list.
	
	The `for' loop executes a sequence of commands for each member in a
	list of items. If `in WORDS ...;' is not present, then `in "$@"' is
	assumed. For each element in WORDS, NAME is set to that element, and
	the COMMANDS are executed.

	Exit Status:
	Returns the status of the last command executed.
for ((: for (( exp1; exp2; exp3 )); do COMMANDS; done
	Arithmetic for loop.

	Equivalent to
		(( EXP1 ))
		while (( EXP2 )); do
			COMMANDS
			(( EXP3 ))
		done
	EXP1, EXP2, and EXP3 are arithmetic expressions. If any expression is
	omitted, it behaves as if it evaluates to 1.
	
	Exit Status:
	Returns the status of the last command executed.

格式1:

for NAME [in WORDS ... ] ; do COMMANDS; done

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

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

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

for循环列表生成方式:

  • 直接给出列表
  • 整数列表:
    {start..end}
    $(seq [start [step]] end)
  • 返回列表的命令:
    $(COMMAND)
  • 使用glob,如:*.sh
  • 变量引用,如:$@每个参数为独立字符串,$#传递给脚本的参数的个数,$*全部参数合为一个字符串

范例:面试题,计算1+2+3+…+100的结果

[root@centos8 ~]#sum=0;for i in {1..100};do let sum+=i;done ;echo sum=$sum
sum=5050
[root@centos8 ~]#seq -s+ 100|bc5050
5050

范例:

[root@centos8 ~]#cat /data/scripts/for_sum.sh
#!/bin/bash
sum=0
for i in $* ; do
	let sum+=i
done
echo sum=$sum

[root@centos8 ~]#bash /data/scripts/for_sum.sh 1 2 3 4 5 6
sum=21

范例: 九九乘法表

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

生产案例:将指定目录下的文件所有文件的后缀改名为 bak 后缀

[root@centos8 ~]#cat /data/script40/for_rename.sh
#!/bin/bash
DIR=/data/test
cd $DIR
for FILE in * ;do
	PRE=`echo $FILE | sed -nr 's/(.*)\.([^.]+)$/\1/p'`
# SUFFIX=`echo $FILE | sed -nr 's/(.*)\.([^.]+)$/\2/p'`
	mv $FILE $PRE.bak
done

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

#1 yyyy-mm-dd10.sh 创建YYYY-MM-DD,当前日期一年前365天到目前共365个目录,里面有10个文件,$RANDOM.log

[root@centos8 ~]#cat for_dir.sh
#!/bin/bash
for i in {1..365};do
	DIR=`date -d "-$i day" +%F`
	mkdir /data/test/$DIR
	cd /data/test/$DIR
	for n in {1..10};do
		touch $RANDOM.log
	done
done

#2 移动到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`
	[ -d $YYYY_MM/$DD ] || mkdir -p $YYYY_MM/$DD &> /dev/null
	mv $DIR/* $YYYY_MM/$DD
done

面试题:扫描一个网段:10.0.0.0/24,判断此网段中主机在线状态,将在线的主机的IP打印出来

[root@centos8 ~]#cat /data/scripts/for_scan_host.sh
#!/bin/bash
NET=10.0.0
for ID in {1..254};do
	{
	ping -c1 -W1 $NET.$ID &> /dev/null && echo $NET.$ID is up || echo $NET.$ID
is down
	}&
done
wait
#{ }& 可以并行执行

格式2

双小括号方法,即((…))格式,适合用于算术运算,双小括号方法也可以使bash Shell实现C语言风格的变量操作,不如第一种更通用

I=10;((I++))
Linux进阶_shell脚本编程之进阶篇_第2张图片

for ((: for (( exp1; exp2; exp3 )); do COMMANDS; done

for ((控制变量初始化;条件判断表达式;控制变量的修正表达式))
do
	循环体
done

说明:

  • 控制变量初始化:仅在运行到循环代码段时执行一次
  • 控制变量的修正表达式:每轮循环结束会先进行控制变量修正运算,而后再做条件判断

范例:

#!/bin/bash
for((sum=0,i=1;i<=100;sum+=i,i++));do
	true
done
echo $sum

范例:九九乘法表

#!/bin/bash
#语法1实现
for i in {1..9};do
	for j in `seq $i`;do
		echo -e "${j}x$i=$((j*i))\t\c"
	done
	echo
done

#语法2实现
for ((i=1;i<10;i++));do
	for((j=1;j<=i;j++));do
		echo -e "${j}x${i}=$[i*j]\t\c"
	done
	echo
done

范例:等腰三角形

[root@centos8]#cat for_triangle.sh
#!/bin/bash
read -p "请输入三角形的行数: " line
for((i=1;i<=line;i++));do
	for((k=0;k<=line-i;k++));do
		echo -e ' \c'
	done
	for((j=1;j<=2*i-1;j++));do
		echo -e '*\c'
	done
	echo
done
[root@centos8]#bash for_triangle.sh
请输入三角形的行数: 10
         *
        ***
       *****
      *******
     *********
    ***********
   *************
  ***************
 *****************
*******************

范例:死循环

[root@centos8 ~]#for((;;));do echo for;sleep 1;done
for
for
for
for
for
for
for

练习:用 for 实现
1、判断/var/目录下所有文件的类型
2、添加10个用户user1-user10,密码为8位随机字符
3、/etc/rc.d/rc3.d目录下分别有多个以K开头和以S开头的文件;分别读取每个文件,以K开头的输出为文件加stop,以S开头的输出为文件名加start,如K34filename stop S66filename 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.3 while循环

格式:

while COMMANDS; do COMMANDS; done

while CONDITION; do
		循环体
done

说明:

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

无限循环

while true; do
	循环体
done

范例:

#配置发邮件的邮箱
[root@centos8 ~]#cat .mailrc
set from=[email protected]
set smtp=smtp.qq.com
set smtp-auth-user=[email protected]
set smtp-auth-password=???
set smtp-auth=login
set ssl-verify=ignore

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

练习:用while实现
1、编写脚本,求100以内所有正奇数之和
2、编写脚本,提示请输入网络地址,如192.168.0.0,判断输入的网段中主机在线状态,并统计在线和离线主机各多少
3、编写脚本,打印九九乘法表
4、编写脚本,利用变量RANDOM生成10个随机数字,输出这个10数字,并显示其中的最大值和最小值
5、编写脚本,实现打印国际象棋棋盘
6、后续六个字符串:efbaf275cd、4be9c40b8b、44b2395c46、f8c8873ce0、b902c16c8b、ad865d2f63是通过对随机数变量RANDOM随机执行命令: echo $RANDOM|md5sum|cut –c1-10后的结果,请破解这些字符串对应的RANDOM值

1.4 until循环

格式:

until COMMANDS; do COMMANDS; done

until CONDITION; do
	循环体
done

说明:

进入条件: CONDITION 为false
退出条件: CONDITION 为true

无限循环

until false; do
	循环体
Done

1.5 循环控制语句 continue

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

格式:

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

范例:

[root@centos8]#cat continue_for.sh
#!/bin/bash
for ((i=0;i<3;i++));do
	for((j=0;j<10;j++));do
		[ $j -eq 3 ] && continue 2
		echo $j
	done
	echo ---------------------------
done
[root@centos8]#bash continue_for.sh
0
1
2
0
1
2
0
1
2

1.6 循环控制语句 break

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

格式:

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

范例:

[root@centos8]#cat break_for.sh
#!/bin/bash
for ((i=0;i<10;i++));do
	for((j=0;j<10;j++));do
		[ $j -eq 5 ] && break
		echo $j
	done
	echo ---------------------------
done

范例:

[root@centos8]#cat break_for.sh
#!/bin/bash
for ((i=0;i<10;i++));do
	for((j=0;j<10;j++));do
		[ $j -eq 5 ] && break 2
		echo $j
	done
	echo ---------------------------
done

[root@centos8 script40]#bash break_for.sh
0 
1 
2 
3 
4

范例:实现菜单

[root@centos8]#cat menu.sh
#!/bin/bash
sum=0
COLOR='echo -e \033[1;31m'
COLOR2='echo -e \033[1;32m'
END="\033[0m"

while true;do
echo -e "\033[33;1m\c"
cat <<EOF
1) 鲍鱼
2) 满汉全席
3) 龙虾
4) 燕窝
5) 帝王蟹
6) 退出
EOF
echo -e "\033[0m"

read -p "请点菜: " MENU
case $MENU in
1|4)
	$COLOR'菜价: $10'$END
	let sum+=10
	;;
3|5)
	$COLOR'菜价: $20'$END
	let sum+=20
	;;
2)
	$COLOR'菜价: $1000'$END
	let sum+=1000
	;;
6)
	$COLOR2"你点的菜总价格是 $sum"$END
	break
	;;
*)
	echo "点错了,没有这道菜"
	;;
esac
$COLOR2"你点的菜总价格是 $sum"$END
done

范例:猜数字游戏

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

1.7 循环控制 shift 命令

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

范例:依次创建用户

[root@centos8]#vim shift_batch_user.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 is exist
	else
		useradd $1
		echo "$1 is created"
	fi
	shift
done
echo "All user is created"

[root@centos8]#bash shift_batch_user.sh
Usage: shift_batch_user.sh user1 user2 ...

[root@centos8]#bash shift_batch_user.sh tom alice jack
tom is exist
alice is exist
jack is created
All user is created

练习
1、每隔3秒钟到系统上获取已经登录的用户的信息;如果发现用户hacker登录,则将登录时间和主机记录于日志/var/log/login.log中,并退出脚本
2、随机生成10以内的数字,实现猜字游戏,提示比较大或小,相等则退出
3、用文件名做为参数,统计所有参数文件的总行数
4、用二个以上的数字为参数,显示其中的最大值和最小值

1.8 while read 特殊用法

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

格式:

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

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

范例:

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

范例:磁盘空间报警发邮件

cat while_read_diskcheck.sh
#!/bin/bash
WARNING=80
MAIL=[email protected]
df |sed -nr "/^\/dev\/sd/s#^([^ ]+) .* ([0-9]+)%.*#\1 \2#p"|while read DEVICE
USE;do
	if [ $USE -gt $WARNING ] ;then
		echo "$DEVICE will be full,use:$USE" | mail -s "DISK WARNING" $MAIL
	fi
done

范例:访问次数异常用户禁止访问

cat while_read_check_dos.sh
#!/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

范例:查看/sbin/nologin的shell类型的用户名和UID

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

1.9 select 循环与菜单

格式:

select NAME [in WORDS ... ;] do COMMANDS; done

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

说明:

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

范例:实现菜单

cat select.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 价格是 166
	let sum+=166
	;;
5)
	echo $MENU 价格是 200
	let sum+=200
	;;
6)
	echo "点菜结束,退出"
	break
	;;
*)
	echo "点菜错误,重新选择"
	;;
esac
done
echo "总价格是: $sum"

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@centos8 ~]#dir() {
> ls -l
> }
[root@centos8 ~]#dir
total 4
-rw-------. 1 root root 1559 Nov 7 19:33 anaconda-ks.cfg
2.2.2 在脚本中定义及使用函数

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

[root@centos8 ~]#cat func1.sh
#!/bin/bash
#name:func1
hello(){
	echo "Hello there today's date is `date +%F`"
}
echo "now going to the function hello"
hello
echo "back from the function"
[root@centos8 ~]#./func1.sh
now going to the function hello
Hello there today's date is 2019-12-18
back from the function

范例:

cat reset.sh
#!/bin/bash
disable_selinux(){
	sed -i.bak 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config
	echo "SElinux已禁用,重新启动后才可生效"
}
disable_firewall(){
	systemctl disable --now firewalld &> /dev/null
	echo "防火墙已禁用"
}
set_ps1() {
	echo "PS1='\[\e[1;35m\][\u@\h \W]\\$\[\e[0m\]'" > /etc/profile.d/reset.sh
	echo "提示符已修改成功,请重新登录生效"
}
set_eth(){
	sed -i.bak '/GRUB_CMDLINE_LINUX=/s#"$# net.ifnames=0"#' /etc/default/grub
	grub2-mkconfig -o /boot/grub2/grub.cfg &> /dev/null
	echo "网络名称已修改成功,请重新启动才能生效"
}

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
2.2.3 使用函数文件
  • 可以将经常使用的函数存入一个单独的函数文件,然后将函数文件载入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
}

范例:

[root@centos8 ]#cat reset.sh
#!/bin/bash
. /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

2.3 函数返回值

函数的执行结果返回值:

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

函数的退出状态码:

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

2.4 环境函数

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

定义环境函数:

export -f function_name
declare -xf function_name

查看环境函数:

export -f
declare -xf

2.5 函数参数

函数可以接受参数:

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

2.6 函数变量

变量作用域:

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

注意:

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

在函数中定义本地变量的方法
local NAME=VALUE·

2.7 函数递归

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

递归示例:

阶乘是基斯顿·卡曼于 1808 年发明的运算符号,是数学术语,一个正整数的阶乘(factorial)是所有小于及等于该数的正整数的积,并且0和1的阶乘为1,自然数n的阶乘写作n!
n!=1×2×3×…×n
阶乘亦可以递归方式定义: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
#
fact() {
	if [ $1 -eq 0 -o $1 -eq 1 ]; then
		echo 1
	else
		echo $[$1*$(fact $[$1-1])]
	fi
}
fact $1

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

函数实现

:(){ :|:& };:
bomb() { bomb | bomb & }; bomb

脚本实现

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

Linux进阶_shell脚本编程之进阶篇_第3张图片

练习

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

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

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

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

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

  6. 编写服务脚本/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为当前脚本名

  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-linux-x86-
    64.so.2 ==> /mnt/sysroot/lib64/ld-linux-x86-64.so.2
    (5)每次复制完成一个命令后,不要退出,而是提示用户键入新的要复制的命令,并重复完成上述功能;直到用户输入quit退出

3 其它脚本相关工具

3.1 信号捕捉 trap

trap ‘触发指令’ 信号

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

范例:

#!/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]#cat trap_exit.sh
#!/bin/bash
finish(){
	echo finish| tee -a /root/finish.log
}

trap finish exit

while true ;do
	echo running
	sleep 1
done

3.2 创建临时文件 mktemp

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

格式

mktemp [OPTION]... [TEMPLATE]

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

常见选项:
-d 创建临时目录
-p DIR或–tmpdir=DIR 指明临时文件所存放目录位置

范例:

[root@centos8 data]#mktemp
/tmp/tmp.UogGCumh8C
mktemp /tmp/testXXX
tmpdir=`mktemp –d /tmp/testdirXXX`
mktemp --tmpdir=/testdir testXXXXXX

范例:

[root@centos8 ~]#cat /data/scripts/rm.sh
#!/bin/bash
DIR=`mktemp -d /tmp/trash-$(date +%F_%H-%M-%S)XXXXXX`
mv $* $DIR
echo $* is move to $DIR

[root@centos8 ~]#alias rm=/data/scripts/rm.sh

3.3 安装复制文件 install

install 功能相当于cp,chmod,chown,chgrp 等相关工具的集合

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
-d DIRNAME 目录

范例:

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

[root@centos8 ~]#install -m 700 -o mage -g daemon -d /data/testdir
[root@centos8 ~]#ll -d /data/testdir
drwx------ 2 mage daemon 6 Apr 29 15:09 /data/testdir

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

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

expect 语法:

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

常见选项:

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

示例:

expect -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

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

多分支模式语法:

[root@centos8 test]#expect
expect1.1> expect "hi" { send "You said hi\n" } "hehe" { send "Hehe yourself\n"
} "bye" { send "Good bye\n" }
hehe
Hehe yourself
expect1.2> expect "hi" { send "You said hi\n" } "hehe" { send "Hehe yourself\n"
} "bye" { send "Good bye\n" }
bye
Good bye
expect1.3> expect "hi" { send "You said hi\n" } "hehe" { send "Hehe yourself\n"
} "bye" { send "Good bye\n" }
hi
You said hi
expect1.4>

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

expect {
	"hi" { send "You said hi\n"}
	"hehe" { send "Hehe yourself\n"}
	"bye" { send " Good bye\n"}
}
[root@centos8 ~]#expect
expect1.1> expect {
+> "hi" { send "You said hi\n"}
+> "hehe" { send "Hehe yourself\n"}
+> "bye" { send " Good bye\n"}
+> }
bye
Good bye
expect1.2>

范例1:

#!/usr/bin/expect
spawn scp /etc/fstab 10.0.0.7:/data
expect {
		"yes/no" { send "yes\n";exp_continue }
		"password" { send "magedu\n" }
}
expect eof

范例2:

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

范例3:expect 变量

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

范例4:expect 位置参数

[root@centos8 scripts]#cat 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@centos8 scripts]#./expect4 10.0.0.7 root magedu
spawn ssh [email protected]
[email protected]'s password:
Last login: Wed Apr 29 15:34:14 2020 from 10.0.0.8
[root@centos7 ~]#exit
logout
Connection to 10.0.0.7 closed.

范例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
#./ssh4.exp 10.0.0.7 root magedu

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

#./ssh5.sh 192.168.8.10 root magedu

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

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

范例:

[root@centos8 scripts]#cat expect8.sh
#!/bin/bash
NET=10.0.0
user=root
password=centos

for ID in 202 204 ;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 "sed -i 's/^SELINUX=enforcing/SELINUX=disabled/'
/etc/selinux/config\n" }
expect "#" { send "setenforce 0\n" }
expect "#" { send "exit\n" }
expect eof
EOF
done

4 数组array

4.1 数组介绍

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

数组名和索引

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

4.2 声明数组

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

注意:两者不可相互转换

4.3 数组赋值

数组元素的赋值

(1) 一次只赋值一个元素
ARRAY_NAME[INDEX]=VALUE

范例:

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

(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

4.4 显示所有数组

显示所有数组:
declare -a

范例:

[root@centos8 ~]#declare -a
declare -a BASH_ARGC=()
declare -a BASH_ARGV=()
declare -a BASH_COMPLETION_VERSINFO=([0]="2" [1]="7")
declare -a BASH_LINENO=()
declare -ar BASH_REMATCH=()
declare -a BASH_SOURCE=()
declare -ar BASH_VERSINFO=([0]="4" [1]="4" [2]="19" [3]="1" [4]="release"
[5]="x86_64-redhat-linux-gnu")
declare -a DIRSTACK=()
declare -a FUNCNAME
declare -a GROUPS=()
declare -a PIPESTATUS=([0]="0")

4.5 引用数组

引用数组元素

${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

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

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

范例:

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

4.6 删除数组

删除数组中的某元素,会导致稀疏格式
unset ARRAY[INDEX]

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

删除整个数组
unset ARRAY

范例:

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

[root@centos8 ~]#

4.7 数组数据处理

数组切片:

${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

向数组中追加元素:

ARRAY[${#ARRAY[*]}]=value
ARRAY[${#ARRAY[@]}]=value

范例:

[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

4.8 关联数组

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

范例:关联数组

[root@centos8 ~]#declare -A student
[root@centos8 ~]#student[name1]=lijun
[root@centos8 ~]#student[name2]=ziqing
[root@centos8 ~]#student[age1]=18
[root@centos8 ~]#student[age2]=16
[root@centos8 ~]#student[gender1]=m
[root@centos8 ~]#student[city1]=nanjing
[root@centos8 ~]#student[gender2]=f
[root@centos8 ~]#student[city2]=anhui
[root@centos8 ~]#student[gender2]=m
[root@centos8 ~]#student[name50]=alice
[root@centos8 ~]#student[name3]=tom
[root@centos8 ~]#for i in {1..50};do echo student[name$i]=${student[name$i]};
done

4.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 "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"

5 字符串处理

5.1 字符串切片

基于偏移量取字符串

#返回字符串变量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}

范例:

[root@centos8 script40]#str=abcdef我你他
[root@centos8 script40]#echo ${#str}
9
[root@centos8 script40]#echo ${str:2}
cdef我你他
[root@centos8 script40]#echo ${str:2:3}
cde
[root@centos8 script40]#echo ${str:-3}
abcdef我你他
[root@centos8 script40]#echo ${str: -3}
我你他
[root@centos8 script40]#echo ${str:2:-3}
cdef
[root@centos8 script40]#echo ${str: -2:-3}
-bash: -3: substring expression < 0
[root@centos8 script40]#echo ${str: -3:-2}[root@centos8 script40]#echo ${str:-3:-2}
abcdef我你他
[root@centos8 script40]#echo ${str: -3:-2}[root@centos8 script40]#echo ${str: -5:-2}
ef我

基于模式取子串

#其中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

范例;

[root@centos8 ~]#url=http://www.magedu.com:8080
[root@centos8 ~]#echo ${url##*:}
8080
[root@centos8 ~]#echo ${url%%:*}
http

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

5.3 查找并删除

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

5.4 字符大小写转换

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

6 高级变量

6.1 高级变量赋值

Linux进阶_shell脚本编程之进阶篇_第4张图片
范例:

[root@centos8 ~]#title=ceo
[root@centos8 ~]#name=${title-mage}
[root@centos8 ~]#echo $name
ceo
[root@centos8 ~]#title=
[root@centos8 ~]#name=${title-mage}
[root@centos8 ~]#echo $name
[root@centos8 ~]#unset title
[root@centos8 ~]#name=${title-mage}
[root@centos8 ~]#echo $name
mage

范例:

[root@centos8 ~]#title=ceo
[root@centos8 ~]#name=${title:-mage}
[root@centos8 ~]#echo $name
ceo
[root@centos8 ~]#title=
[root@centos8 ~]#name=${title:-mage}
[root@centos8 ~]#echo $name
mage
[root@centos8 ~]#unset title
[root@centos8 ~]#name=${title:-mage}
[root@centos8 ~]#echo $name
mage

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

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

declare [选项] 变量名

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

6.3 变量间接引用

6.3.1 eval命令

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

范例:

[root@centos8 ~]# CMD=whoami
[root@centos8 ~]# echo $CMD
whoami
[root@centos8 ~]# eval $CMD
root
[root@centos8 ~]# n=10
[root@centos8 ~]# echo {0..$n}
{0..10}
[root@centos8 ~]# eval echo {0..$n}
0 1 2 3 4 5 6 7 8 9 10
[root@centos8 ~]#for i in `eval echo {1..$n}` ;do echo i=$i ;done
i=1
i=2
i=3
i=4
i=5
i=6
i=7
i=8
i=9
i=10
[root@centos8 ~]#i=1
[root@centos8 ~]#j=a
[root@centos8 ~]#$j$i=hello
-bash: a1=hello: command not found
[root@centos8 ~]#eval $j$i=hello
[root@centos8 ~]#echo $j$i
a1
6.3.2 间接变量引用

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

variable1=variable2
variable2=value

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

eval tempvar=\$$variable1
tempvar=${!variable1}

范例:

[root@centos8 ~]#ceo=name
[root@centos8 ~]#name=mage
[root@centos8 ~]#echo $ceo
name
[root@centos8 ~]#echo $$ceo
33722ceo
[root@centos8 ~]#echo $BASHPID
33722
[root@centos8 ~]#echo \$$ceo
$name
[root@centos8 ~]#eval tmp=\$$ceo
[root@centos8 ~]#echo $tmp
mage

[root@centos8 ~]#echo ${!ceo}
mage
[root@server ~]# NAME=wangxiaochun
[root@server ~]# N=NAME
[root@server ~]# N1=${!N}
[root@server ~]# echo $N1
wangxiaochun
[root@server ~]# eval N2=\$$N
[root@server ~]# echo $N2
wangxiaochun
6.3.3 变量引用reference
[root@centos8 ~]#cat test.sh
#!/bin/bash
ceo=mage
title=ceo
declare -n ref=$title
[ -R ref ] && echo reference
echo $ref
ceo=wang
echo $ref
[root@centos8 ~]#bash test.sh
reference
mage
wang

shell脚本编程之基础篇

链接跳转:SHELL脚本编程之基础篇

你可能感兴趣的:(#,Linix进阶)