目录
一、shell函数
1、shell函数的概念
在shell脚本中,函数是一组被命名的命令序列,可以在脚本中多次调用。
使用函数可以将一段常用的代码逻辑封装起来,提高代码的可读性和重用性
函数的组成:函数名和函数体
帮助:help function
2、shell函数的用法
先定义函数,再调用函数
2.1 定义函数
#定义函数方法一:
function 函数名 {
命令序列
}
#定义函数方法二:
函数名 () {
命令序列
}
#定义函数方法三
function 函数名 () {
命令序列
}
2.2 调用函数
#在定义函数后添加函数名,即调用函数
函数名
2.3 函数作用范围
2.3.1 调用函数的范围
函数调用虽然可以出现在脚本的任何位置,但前提必须是在调用之前已经定义了相应的函数
2.3.2 全局作用域和局部作用域
局部作用域:函数内部使用local
关键字声明的变量默认为局部变量,只在函数内部有效。这意味着函数内的变量不会影响到函数外部的同名变量,也不会被函数外部的代码访问到
#local关键字用于声明局部变量
全局作用域:如果在函数内部没有使用local
关键字声明变量,则该变量默认将成为全局变量,在整个脚本中都可见和访问。全局变量可以在函数内部创建,并且可以在函数内部和外部使用
函数在Shell脚本中仅在当前Shell环境中有效,而Shell脚本中变量默认全局有效
3、 函数返回值
3.1 系统默认的返回值
#检测输入的ip地址是否符合规范
#!/bin/bash
IPADDRESS () {
read -p "请输入IP地址: " ip_address
# 定义IP地址的正则表达式模式
ip_pattern="^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$"
# 使用正则表达式进行匹配
if [[ $ip_address =~ $ip_pattern ]]; then
echo "IP地址格式正确"
else
echo "IP地址格式错误"
fi
}
IPADDRESS
注:
函数一结束就取返回值,因为$?变量只返回执行的最后一条命令的退出状态码
系统默认只要能执行脚本,无论脚本使用是否出错,使用
$?
特殊变量获取返回值都是0值如果想通过返回值向调用者提供错误或正确的结果,就需要使用
return
语句来指定返回值
3.2 return语句
使用return
语句来指定函数的返回值。可以将任意整数值作为返回值,但退出状态码必须是0~255,超出时值将为除以256取余
通常用非零值表示错误或异常情况,而零表示成功或正常情况
4、函数传参
函数传参是指将参数传递给一个函数来供函数内部使用
当你定义一个函数时,可以在函数名后的括号内指定参数列表。这些参数将被视为函数的输入,并且可以在函数体内使用
函数传参的目的是让函数能够接受外部提供的数据或信息,以便在函数内部进行处理和操作。通过传递参数,可以使函数更加灵活和通用,可以根据不同的输入执行相同的操作
5、查看函数列表
declare -F #查看当前已定义的函数名
declare -f #查看当前已定义的函数定义
declare -f 函数名 #查看当前已定义的函数名定义
declare -F 函数名 #查看指定当前已定义的函数名
6、删除函数
unset 函数名 #删除定义过的函数
7、 函数递归
函数递归是指函数调用自身的过程。递归函数可以通过重复调用自身来解决复杂的问题,将大问题分解成更小的子问题,并通过递归调用来解决这些子问题
7.1 函数递归死循环
函数递归指的是在函数内部调用自身的过程,如果不正确使用,函数递归可能会导致死循环,即函数无限地调用自身,直到耗尽系统资源或达到某个限制
7.1.1 模拟死循环
[root@localhost ~]# wx () { echo $i;echo "run fast";let i++;wx; }
[root@localhost ~]# wx
7.1.2 fork炸弹
fork 炸弹是一种恶意程序,它的内部是一个不断在 fork 进程的无限循环,实质是一个简单的递归程序
由于程序是递归的,如果没有任何限制,这会导致这个简单的程序迅速耗尽系统里面的所有资源
[root@localhost ~]#bomb() { bomb | bomb & }; bomb
[root@localhost ~]# :(){ :|:& };:
7.2 阶乘
阶乘是一个正整数的乘积,从该正整数开始递减,直到1。通常用符号"!"表示。例如,n的阶乘可以表示为n!,计算方式为n! = n * (n-1) * (n-2) * ... * 3 * 2 * 1。其中,0的阶乘定义为1
方法一:利用函数递归法求阶乘
使用函数递归来计算阶乘,可以通过重复调用自身来解决阶乘这个复杂的问题
#接受一个参数 $num,表示要计算阶乘的数。函数内部使用条件语句判断基本情况,即当输入的数小于等于1时,直接返回1。否则,它会递归地调用自身来计算$num-1的阶乘,并将结果与$num相乘,然后返回结果
#!/bin/bash
jc () {
read -p "请输入一个正整数:" num
if [ $num -eq 1 ];then
echo 1
else
local temp=$[$num-1]
local result=`jc $temp`
echo $[$num*result]
fi
}
jc $num
bash $0
方法二:利用for循环求阶乘
#!/bin/bash
read -p "请输入一个正整数:" num
sum=1
i=1
for i in `seq $num`
do
let sum=$[sum*i]
done
echo $sum
bash $0
注:
基本情况:递归函数必须包含一个基本情况,即函数停止调用自身的条件。如果没有基本情况,递归函数将无限循环下去,导致栈溢出
递归调用:在函数内部,通过调用相同的函数来实现递归。递归调用必须在满足基本情况之前发生,以确保递归能够终止
二、shell数组
1、shell 数组的概念
在Shell脚本中,数组是一种特殊的变量类型,用于存储一系列的值(元素),你可以使用括号来创建和初始化数组
变量和数组:
- 变量:用一个固定的字符串,代替一个不固定字符串
- 数组:用一个固定的字符串,代替多个不固定字符串
数组的类型:
- 普通数组:只能使用整数作为数组索引
- 关联数组:可以使用字符串作为数组索引
数组名和索引:
- 索引的编号(下标)从0开始,属于数值索引
- 索引可支持使用自定义的格式,而不仅是数值格式,即为关联索引,bash 4.0版本之后开始支持bash的数组支持稀疏格式(索引不连续)
2、shell 数组的用法
2.1 声明数组
2.1.1 普通数组
普通数组是一系列按照索引顺序排列的元素集合。在shell脚本中,普通数组的索引从0开始。可以通过索引来访问数组中的元素
#申明普通数组,shell脚本默认普通数组,可以不使用命令申明直接使用普通数组
declare -a 数组名
2.1.2 关联数组
关联数组是根据键值对存储的数组,也被称为字典或哈希表。在shell脚本中,关联数组的键可以是任意字符串,而不仅仅是数字。可以通过键来访问数组中的值
#申明关联数组,使用关联数组前必须使用命令申明
declare -A array_name
2.2 定义数组
方法一:一次赋值全部元素
数组名=(value0 value1 value2 value3 ......)
方法二:一次只赋值一个元素(为了方便修改或追加元素)
数组名[0]="value1"
数组名[1]="value2"
数组名[2]="value3"
……………………………
方法三:综合方法一和方法二,赋值特定元素
数组名=([0]=value [1]=value1 [2]=value2 ......)
方法四:元素赋值给列表,引用列表加入组
变量名="value0 value1 value2 value3 ...... "
数组名= $变量名
方法五:交互式
read -a 数组名
2.3 读取数组信息
2.3.1 获取数组长度
#在数组前添加#,获取数组长度
[root@localhost data]#echo ${#a[@]}
[root@localhost data]#echo ${#a[*]}
2.3.2 获取数组列表
[root@localhost data]#echo ${a[@]}
[root@localhost data]#echo ${a[*]}
2.3.3 读取下标赋值
[root@localhost data]#echo ${a[1]}
[root@localhost data]#echo ${a[2]}
2.3.4 读取数组下标
[root@localhost data]#echo ${!a[@]}
[root@localhost data]#echo ${!a[*]}
拓展:
不知道数组的下标,如何追加元素?
#使用数组总长度为下标进行赋值
[root@localhost data]#a[${#a[@]}]=6
2.4 遍历数组
遍历数组意味着逐个访问数组中的元素,这通常用于对数组中的每个元素执行相同的操作,比如打印它们或者进行某种计算
#!bin/bash
a=(10 20 30 40 50)
for i in ${a[@]} #挨个赋值,循环五次
do
echo i=$i
done
2.5 数组元素的切片
可以使用数组切片来获取数组的子集,即从一个数组中获取一部分元素的操作
这种操作允许你创建一个新的数组,其中包含原始数组中特定范围的元素
${a[*]:n:m} #提取从索引下标n开始的m个元素
${a[*]:n} #提取从索引下标n开始的所有元素
${a[@]: -n}
#取数组倒数几个元素,注意:冒号后必须有一空白字符
2.6 元素的临时替换
echo ${数组名[@]/原始字符/替换字符} #只是象征性的更换,实际并没有改变原有内容
2.7 删除数组
unset 数组名 #删除数组
unset 数组名[下标] #删除指定的数组元素
3、数组最值
3.1 数组的最大值
先定义了一个包含一些整数的数组(交互输入)。然后,假设数组的第一个元素是最大值。接下来,我们使用for
循环遍历数组中的每个元素,并将其与当前的最大值进行比较。如果当前元素大于最大值,则更新最大值。最后,打印出最大值
循环次数小于数组长度(即小于数值元素个数,比较n-1次),每次加1
#!/bin/bash
read -p "请输入数组的整数值并以空格隔开:" num
a=($num)
l=${#a[@]}
max=${a[0]}
for ((i=0;i<$l;i++))
do
if [[ $max -lt ${a[$i+1]} ]];then
max=${a[$i+1]}
fi
done
echo "最大值为:$max"
3.2 数组的最小值
先定义了一个包含一些整数的数组(交互输入)。然后,假设数组的第一个元素是最小值。接下来,我们使用for
循环遍历数组中的每个元素,并将其与当前的最小值进行比较。如果当前元素小于最小值,则更新最小值。最后,打印出最小值
循环次数小于数组长度(即小于数值元素个数,比较n-1次),每次加1
#!/bin/bash
read -p "请输入数组的整数值并以空格隔开:" num
a=($num)
l=${#a[@]}
min=${a[0]}
for ((i=0;i<$l-1;i++))
do
if [[ $min -ge ${a[$i+1]} ]];then
min=${a[$i+1]}
fi
done
echo "最小值为:$min"
3.3 随机数组的最大最小值
首先生成一个包含10个随机整数的数组。然后,通过遍历数组,使用两个变量max
和min
来记录当前的最大值和最小值。最后,打印出最大值和最小值
#!/bin/bash
for i in {0..10}
do
a[$i]=$RANDOM
[ $i -eq 0 ] && min=${a[0]} && max=${a[0]}
[ ${a[$i]} -gt $max ] && max=${a[$i]}
[ ${a[$i]} -lt $max ] && min=${a[$i]}
done
echo ${a[@]}
echo "最大值为:$max"
echo "最小值为:$min"
4、冒泡排序
- 数组排序算法
冒泡排序类似气泡上涌的动作,会将数据在数组中从小到大或者从大到小不断的向前移动
- 冒号排序基本思想
对比相邻的两个元素值,如果满足条件就交换元素值,把较小的元素移动到数组前面,把大的元素移动到数组后面(也就是交换两个元素的位置)这样较小的元素就像气泡一样从底部上升到顶部
- 算法思路
冒泡算法由双层循环实现,其中外部循环用于控制排序轮数,一般循环次数小于数组长度(即小于数值元素个数,比较n-1次),每次加1。而内部循环主要用于对比数组中每个相邻元素的大小,以确定是否交换位置对比和交换次数随排序轮数而减少
第一轮比较(n个数):找出最大值,将最大值放到最后,比n-1次
第二轮比较(n-1个数):找出最大值,将最大值放到最后,比n-2次
第三轮比较(n-2个数):找出最大值,将最大值放到最后,比n-3次
……
最后一轮比较(最后两个数):找出最大值,将最大值放到最后,比1次
#!/bin/bash
a=(1 5 2 0 6 4 8)
l=${#a[@]}
for ((i=1;i<$l;i++)) #轮次循环
do
for ((j=0;j<$l-i;j++)) #相邻数对比循环
do
first=${a[$j]} #定义第一个数
k=$[j+1] #定义变量代表后一个数
second=${a[$k]} #给第二个数赋值
if [ $first -gt $second ];then
temp=$first #如果第一个数大于第二个数,第一个数作为最大临时值
a[$j]=$second #第二个数的值赋给第一个数
a[$k]=$temp #临时值赋给第二个数,完成位置调换
fi
done
done
echo ${a[@]}
冒泡排序随机十个数
#!/bin/bash
for i in {0..10}
do
a[$i]=$RANDOM
done
l=${#a[@]}
for ((i=1;i<l;i++))
do
for ((j=0;j<l-i;j++))
do
first=${a[$j]}
k=$[j+1]
second=${a[$k]}
if [[ $first -gt $second ]]
then
temp=$first
a[$j]=$second
a[$k]=$temp
fi
done
done
echo ${a[@]}
5、随机点名
要实现随机点名的功能,可以使用Shell脚本中的随机数生成函数和数组来实现
#!/bin/bash
namefile="/data/name.txt"
linenum=$(sed -n '$=' $namefile)
while :
do
clear
tmp=$(sed -n "$[RANDOM%linenum+1]p" $namefile)
echo -e "\E[32m 随机点名(ctrl+c停止): \E[0m"
echo -e "\E[32m########################\E[0m"
echo -e "\E[32m# #\E[0m"
echo -e "\E[32m $tmp \E[0m"
echo -e "\E[32m# #\E[0m"
echo -e "\E[32m########################\E[0m"
sleep 0.03
done