Linux Shell编程之函数与数组

在Linux Shell编程中,函数和数组是提升脚本模块化、复用性和数据处理能力的核心要素。函数能将重复执行的代码块封装为独立单元,让脚本结构更清晰;数组则为批量存储、处理一组相关数据提供了便捷的方式。掌握二者的使用,是编写高效、可维护Shell脚本的基础,本文将简易讲解Shell函数与数组的核心用法,并补充实用拓展知识点。

一、Shell函数

Shell函数本质是一组Shell命令的集合,被调用时直接从内存执行,相比重复编写命令块,执行效率更高,同时能大幅减少代码冗余,便于脚本的修改和维护。

1. 函数的基本定义与调用

Shell函数定义有简洁式完整式两种,核心语法一致,可根据习惯选择,function关键字可省略,函数体必须用{}包裹,且命令序列末尾建议加;或换行分隔。

定义语法

# 方式1:省略function(推荐,更简洁)

函数名() {

    命令序列

    [return 退出值]

}

# 方式2:带function关键字

function 函数名() {

    命令序列

    [return 退出值]

}

  • return:用于退出函数并返回一个0-255的整数退出值,0表示执行成功,非0表示失败;若省略,函数将以最后一条命令的执行结果作为返回值。也可以使用exit终止整个shell脚本。

  • exit的区别:return仅退出函数,不影响脚本整体执行;exit会直接终止整个Shell脚本

  • "{"符号表示函数执行命令的入口,该符号可以与函数名同行也可以在函数名下一行的句首;

  • "}"符号表示函数体结束,两个大括号之间{ }是函数体;

  • "function"关键字表示定义一个函数,可以省略;

  • "命令序列"部分可以是任意的shell命令,也可以调用其他函数;

调用语法

直接通过函数名+参数(可选) 调用,无需加括号,参数之间用空格分隔:

函数名 [参数1] [参数2] ...

示例:两数求和
vim sum.sh
#!/bin/bash 
# 定义sum函数
sum() {
     #提示用户输入第一个数
     echo "请输入第一个数:"
     read num1
     #检查输入是否为有效的数字
     if ! [[ $num1 =~ ^[0-9+$ ]]; then
            echo “输入的第一个数不是有效的整数,请重新运行脚本并输入正确的数字。”
            return 1
      fi
      #提示用户输入第二个数
      echo “请输入第二个数:”
      read num2
      #检查输入是否为有效的数字
      if ! [[ $num2 =~ ^[0-9+$ ]]; then
            echo “输入的第一个数不是有效的整数,请重新运行脚本并输入正确的数字。”
            return 1
      fi
       #计算两数之和
        result=$((num1 + num2))
        #输出结果
        echo "两数之和为:$result"
       }
    #调用sum函数
sum


运行脚本:bash sum.sh
示例2:系统资源监控并报警函数
该函数会定期监控系统的cpu和内存使用率,当使用率超过设定的阈值时,会发送报警信息,这里简单模拟为输出到控制台,实际应用中可扩展为发送邮件,短信等。

vim jiankong.sh
#!/bin/bash
#发送报警信息的函数
send_alert() {
        local message=$1
        echo "ALERT: $message"
        #这里可以添加发送邮件,短信等报警逻辑
}

#系统资源1监控函数
monitor_system_rescources() {
      local cpu_thershold=$1
      local mem_thershold=$2
      local interval=$3

while true; do
    #获取cpu使用率
     cpu_usage=$(top -bn1 | grep "Cpu(s)" | awk '{print $2 + $4}')
    # 获取内存使用率
      mem_usage=$(free | awk '/Mem/ {printf("%.2f, $3/$2 * 100)}')
      
     if (( $(echo "$cpu_usage > $cpu_threshold" | bc -1) )); then
            send_alert "CPU usage ($cpu_usage%)."
     fi

     if (( $(echo "$mem_usage > $mem_threshold" | bc -1) )); then
              send_alert "Memory usage ($mem_usage%)."
      fi

sleep "$interval"
done
}


#调用函数
monitor_system_rescources 8 1 5









参数说明:函数接收三个参数,分别是CPU使用率阈值,内存使用率已经监控间隔时间(秒)。
循环监控:使用while true循环不断监控系统资源。通过top和free命令获取cpu和内存使用率,使用bc命令进行浮点数比较。
报警处理:当使用率超过阈值时,调用send_alert函数发送报警信息。


2. 函数变量的作用域

Shell函数默认在当前脚本的全局环境中执行,函数内未特殊声明的变量为全局变量,脚本内任何位置都可访问;若需将变量限定在函数内部,避免与全局变量重名冲突,需用local关键字定义局部变量

核心规则
  • 局部变量:local 变量名=值,仅在函数内部有效,函数执行结束后自动销毁。

  • 全局变量:直接定义变量名=值,脚本内所有位置(包括函数内)均可访问和修改。

示例
vim fun_scope.sh
#!/bin/bash 
# 全局变量 
a=10 
# 定义函数 
test_var() { 
     local a=20 # 局部变量 
     echo "函数内的a:$a" # 输出20 
} 
# 调用函数 
test_var 
echo "函数外的a:$a" # 输出10,全局变量未被修改


执行脚本:bash fun_scope.sh

3. 函数的参数传递

Shell函数不支持在定义时声明参数,而是在调用时通过位置参数传递,函数内部通过$n(n为数字)获取参数,规则与脚本的位置参数一致:

  • $1$2...$9:表示第1到第9个参数;

  • ${10}${11}...:第10个及以后的参数,必须用大括号包裹,否则无法识别;

  • 辅助参数:$#获取参数个数,$@/$*获取所有参数。

示例:计算多个数的和
#!/bin/bash
# 定义求和函数,接收任意多个数字参数 
sum_many() {
    local total=0 
    # 遍历所有参数 
    for num in $@ 
    do 
       total=$((total + num)) 
    done 
     echo "所有参数的和为:$total" 
} 
# 调用函数,传递5个参数 
sum_many 1 2 3 4 5
4. 递归函数

递归函数是指在函数内部调用自身的函数,需设置递归终止条件,否则会陷入无限递归。Shell递归函数常用于遍历目录、计算阶乘、斐波那契数列等场景。

示例1:计算n的阶乘
#!/bin/bash 
# 阶乘递归函数,n! = n*(n-1)!,终止条件:1! = 1 
factorial() { 
     local n=$1
     if [ $n -eq 1 ]; then 
         echo 1 
else 
     # 递归调用,计算(n-1)的阶乘 
     local pre=$(factorial $((n-1))) 
     echo $((n * pre)) 
   fi 
} # 调用函数,计算5的阶乘


factorial 5
示例2:递归遍历目录(补充:显示层级)
在原文档基础上补充层级缩进,让遍历结果更清晰:

#!/bin/bash 
# 递归遍历目录,$1为目录路径,$2为层级缩进符 
traverse_dir() {
    local dir=$1
    local indent=$2
    # 遍历目录下所有内容
    for item in $dir/* 
   do 
     if [ -d $item ]; then
       echo "${indent}├── 目录:$(basename $item)" 
      # 递归调用,缩进符加两个空格
     traverse_dir $item "${indent} "
   elif [ -f $item ]; then 
     echo "${indent}├── 文件:$(basename $item)" 
fi 
done 
}
 # 调用函数,从当前目录开始,初始缩进为空


 traverse_dir "." ""

5. 补充知识点:函数的返回值进阶

原文档仅提及return返回值,实际使用中,return仅能返回0-255的整数,若需返回字符串、大数值等,可通过标准输出(echo) 结合变量接收实现,这是Shell函数返回复杂结果的常用技巧。

示例:返回字符串结果
#!/bin/bash
# 定义函数,返回系统简单信息 
get_sys_info() { 
      local hostname=$(hostname) 
      local kernel=$(uname -r)
      # 通过echo输出结果,作为函数的"返回值" 
     echo "主机名:$hostname,内核版本:$kernel" 
}
 # 用变量接收函数的标准输出 
sys_info=$(get_sys_info)
 echo "系统信息:$sys_info"

二、Shell数组

Shell仅支持一维数组,无需提前定义数组长度,元素下标从0开始,可存储整数、字符串等任意类型数据,是批量处理数据的核心工具。数组的定义和操作语法简洁,支持遍历、切片、替换、删除等常用操作。

1. 数组的4种定义方法

Shell数组用()表示,元素之间用空格分隔,以下4种定义方法可根据场景灵活选择,其中方法1是最常用的简洁式。

方法1:直接定义(推荐)

数组名=(值1 值2 值3 ...)

方法2:指定下标定义(可非连续下标)

数组名=([0]=值1 [2]=值2 [5]=值3 ...)

方法3:通过列表字符串定义

list_str="值1 值2 值3" 数组名=($list_str)

方法4:单个下标赋值(适合动态添加元素)

数组名[0]=值1 数组名[1]=值2 数组名[2]=值3

注意:若元素包含空格,需用单/双引号包裹,例如arr=("hello world" test 123)

2. 数组的操作

Shell数组的核心操作包括获取长度、读取元素、遍历元素,是后续复杂操作的基础,语法固定且易记。

(1)获取数组长度

通过${#数组名[@]}${#数组名[*]}实现,二者效果完全一致,返回数组的元素个数。

arr_number=(1 2 3 4 5) 
arr_length=${#arr_number[*]}  或 arr_length_1=${#arr_number[@]}
echo $arr_length                 echo $arr_length_1
5
(2)读取指定下标元素

语法:${数组名[下标]},下标从0开始;若下标为@*,表示读取数组所有元素。

arr_index2=${arr_number[2]}   (第3个元素)
echo $arr_index2
3
(3)遍历数组元素

最常用for循环遍历,结合${数组名[@]}获取所有元素,适合对每个元素做批量处理。

vim arrary_traverse.sh

#!/bin/bash 
arr_number=(1 2 3 4 5) 
for v in ${arr_number[@]}
do
     echo $v
done


bash arrary_tranverse.sh  (执行脚本)
1
2
3
4
5

3. 数组的高级操作

Shell数组支持切片、替换、删除等高级操作,无需额外命令,通过内置语法即可实现,大幅提升数据处理效率。

(1)数组切片

从数组中提取连续的部分元素,形成新的元素集合(返回字符串,空格分隔)。

语法${数组名[@/*]:起始下标:截取长度}

  • 起始下标:从0开始,若为负数,从数组末尾开始计数(如-2表示倒数第2个元素);

  • 截取长度:可选,若省略,截取到数组末尾。

arr=(1 2 3 4 5)
echo ${arr[@]}   (输出整个数组)
1 2 3 4 5

echo ${arr[@]:0:2}  (${数组名[@或*]:起始位置:长度})
1 2

echo ${arr[@]:2:3}
3 4 5

将数组切片之后,返回的是字符串,以空格作为分隔符
(2)数组替换

将数组中指定的元素内容替换为新内容,默认不修改原数组,仅返回替换后的结果;若需修改原数组,需将结果重新赋值给原数组。

语法${数组名[@/*]/旧内容/新内容}

arr=(1 2 3 4 5)
echo ${arr[@]/4/66}   (${数组名[@或*]/查找字符/替换字符})
1 2 3 66 5


echo ${arr[@]}   (并不会替换数组原有内容)
1 2 3 4 5


arr=(${arr[@]/4/66})            (要改变原有数组,可通过重新赋值实现)
echo ${arr[@]}
1 2 3 66 5
(3)数组删除

通过unset命令实现,可删除单个元素整个数组,删除单个元素后,数组下标不会自动重排。

arr=(1 2 3 4 5)
unset arr  (删除数组)


echo ${arr[*]}
arr=(1 2 3 4 5)
unset arr[2]    (删除第三个元素)



echo ${arr[*]} 
1 2 4 5

4. 补充知识点:数组的常用拓展操作

在原文档基础上,补充3个实际开发中高频使用的数组操作,提升脚本实用性:

(1)动态添加数组元素

直接给未定义的下标赋值,即可实现元素的动态添加,无需提前声明长度。

#!/bin/bash 
arr=(1 2 3) 
arr[3]=4 # 添加第4个元素 
arr[4]=5 # 添加第5个元素 
echo "动态添加后:${arr[@]}" # 输出1 2 3 4 5
(2)数组去重

利用awksort+uniq命令,实现数组中重复元素的去重,适用于批量数据去重场景。

#!/bin/bash 
arr=(1 2 2 3 3 3 4) 
# 数组去重,重新赋值给原数组 
arr=($(echo "${arr[@]}" | tr ' ' '\n' | sort -u | tr '\n' ' ')) 
echo "去重后数组:${arr[@]}" # 输出1 2 3 4
(3)数组合并

将多个数组合并为一个新数组,直接通过()拼接即可,语法简洁。

#!/bin/bash
arr1=(1 2 3) 
arr2=(4 5 6) 
arr3=("a" "b")
# 合并数组 
new_arr=(${arr1[@]} ${arr2[@]} ${arr3[@]}) 
echo "合并后数组:${new_arr[@]}" # 输出1 2 3 4 5 6 a b

三、Shell脚本调试(简易版)

编写函数和数组的复杂脚本时,难免出现语法错误或逻辑问题,Shell提供了轻量的调试方式,无需额外工具,通过bash参数set命令即可快速定位问题,核心调试技巧如下:

1. 整脚本报错检查

直接通过bash命令的参数运行脚本,检查语法或执行过程中的错误,核心参数:

# 仅检查语法错误,不执行脚本(推荐先做语法检查)

bash -n 脚本名.sh

# 执行脚本并输出所有执行的命令(调试核心,查看执行过程)

bash -x 脚本名.sh

# 在执行脚本时,先将脚本的内容输出到屏幕上然后执行脚本,如有错误,会提示

bash -v 脚本名.sh

2. 脚本局部调试

若仅需调试脚本中的某一段代码(如函数、数组遍历),可通过set命令开启/关闭局部调试,避免输出过多无关信息:

set -x # 开启调试模式,后续命令执行时会输出详细过程

# 需调试的代码段(如函数调用、数组操作)

set +x # 关闭调试模式,后续命令恢复正常执行

3. 简易调试技巧:echo打印

在函数或数组操作的关键位置,通过echo打印变量值(如数组元素、函数参数),是最直观的调试方式,适合快速定位变量赋值、参数传递的问题。

#!/bin/bash 
arr=(1 2 3) 
# 调试:打印数组所有元素 
echo "数组元素:${arr[@]}" 
sum=0 
for num in ${arr[@]} 
do
    sum=$((sum + num))
    # 调试:打印每次循环的sum值
    echo "当前sum:$sum" 
done

四、核心总结

  1. 函数:核心是封装与复用,支持参数传递、递归调用,通过local定义局部变量避免冲突,复杂返回值可通过echo+变量接收实现,是脚本模块化的核心。

  2. 数组:仅支持一维,无需定义长度,4种定义方法灵活,核心操作包括获取长度、遍历、切片、替换、删除,补充的动态添加、去重、合并是实际开发的高频技巧。

  3. 调试:优先用bash -n做语法检查,再用bash -xset -x做执行过程调试,echo打印是最简易的定位问题方式。

函数和数组的结合使用,能大幅提升Shell脚本的处理能力,例如用函数封装业务逻辑,用数组存储批量数据,再通过循环遍历数组调用函数,可实现高效的批量处理场景(如批量监控服务器、批量处理文件、批量计算数据)。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐