当前位置: 首页 > news >正文

Shell编程之函数与数组

目录

一:Shell函数

1:函数的用法

(1)两个数求和

(2)系统资源监控并报警函数

2:函数变量的作用范围

1.变量作用范围类型

2.作用范围规则表格

3:函数的参数 

1.函数参数基础

2.参数传递模式对比

4:递归函数

1.递归函数关键要素

2.递归优化技巧

二:Shell数组

1. 索引数组(普通数组)

2. 关联数组(键值对)

(1)获取数组长度

(2) 读取某下标赋值

(3)数组遍历

(4)数组切片

(5)数组替换

(6)数组删除

三: Shell脚本调试

1.基础调试方法

2.调试工具

3.常见错误排查表

4.性能调试

本章总结


一:Shell函数

1:函数的用法

                  Shell函数可用于存放一系列的指令。在 Shell脚本执行的过程中,函数被置于内存中,每次调用函数时不需要从硬盘读取,因此运行的速度比较快。在 Shell编程中函数并非是必须的元素,但使用函数可以对程序进行更好的组织。将一些相对独立的代码变成函数,可以提高程序可读性与重用性,避免编写大量重复代码。

Shell函数定义的方法如下所示:
[function] 函数名(){

命令序列

[return x]

}

1. “function”关键字表示定义一个函数,可以省略;

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

3. “}”符号表示函数体结束,两个大括号之间{}是函数体;

4.  “命令序列”部分可以是任意的 Shell命令,也可以调用其他函数;

5. “return”表示退出函数返回一个退出值,通过返回值判断执行是否成功,也可以使用 exit 终止整个 Shell脚本。

       Shell函数调用的方法为:函数名·[参数1]    [参数2]。 下面通过具体的示例学习函数的定义与调用。

(1)两个数求和

               使用 Shell脚本实现两个数相加求和,通过定义函数的方式来完成。sum 函数内部通过 read 命令接收用户分别输入的两个数,然后做加法运算,最后通过调用函数的方式来输出两个数的和。

功能语法示例说明
定义函数function_name() { commands; } 或 function function_name { commands; }greet() { echo "Hello $1"; }两种定义方式等效,第一种更常用
调用函数function_name [arguments]greet "World"像普通命令一样调用,可以传参数
返回值return [n]return 0返回状态码(0-255),0表示成功
获取返回值$?echo $?获取上一条命令/函数的返回值
参数传递$1, $2, ..., $necho "First arg: $1"函数内使用位置参数获取传入的参数
参数个数$#echo "Args count: $#"获取传递给函数的参数个数
所有参数$@ 或 $*for arg in "$@"; do ...获取所有参数,"$@"保留各参数独立性
局部变量local var=valuelocal name=$1函数内使用local声明局部变量
导出函数export -f function_nameexport -f greet使函数在子shell中可用

(2)系统资源监控并报警函数

                 该函数会定期监控系统的 CPU 和内存使用率,当使用率超过设定的阈值时,会发送报警信息,这里简单模拟为输出到控制台,实际应用中可扩展为发送邮件、短信等。

功能函数实现报警方式示例调用说明
CPU使用率监控`check_cpu() { local threshold=1;localusage=1;localusage=(top -bn1grep "Cpu(s)"sed "s/.[0−9.]∗[0−9.]∗% id./\1/"awk '{print 100 - (echo "usage>usage>threshold"bc) -eq 1 ] && return 1return 0; }`返回值`check_cpu 90 && echo "CPU正常"echo "CPU超过阈值"`监控CPU使用率是否超过阈值
内存使用监控`check_mem() { local threshold=1;localfree=1;localfree=(free -mawk '/Mem:/{print (free -mawk '/Mem:/{print ((100 - free*100/total)); [ usage−gtusage−gtthreshold ] && return 1return 0; }`返回值`check_mem 85 && echo "内存正常"echo "内存超过阈值"`监控内存使用百分比
磁盘空间监控`check_disk() { local path=1;localthreshold=1;localthreshold=2; local usage=(df−h(df−hpathawk 'NR==2{print $5}'tr -d '%'); [ usage−gtusage−gtthreshold ] && return 1return 0; }`返回值`check_disk "/" 90 && echo "磁盘正常"echo "磁盘空间不足"`监控指定挂载点磁盘使用率
进程存在性检查`check_process() { local process=1;pgrep−x1;pgrep−xprocess >/dev/null && return 0return 1; }`返回值`check_process "nginx" && echo "Nginx运行中"echo "Nginx未运行"`检查指定进程是否运行
端口监听检查`check_port() { local port=$1; netstat -tulngrep -q ":$port " && return 0return 1; }`返回值`check_port 80 && echo "80端口已监听"echo "80端口未监听"`检查指定端口是否被监听
发送邮件报警`send_alert() { local subject=1;localbody=1;localbody=2; echo "$body"mail -s "$subject" admin@example.com; }`邮件send_alert "CPU警报" "CPU使用率超过90%"使用mail命令发送报警邮件
发送HTTP通知send_webhook() { local url=$1; local message=$2; curl -X POST -H 'Content-Type: application/json' -d "{\"text\":\"$message\"}" $url; }Webhooksend_webhook "https://hooks.example.com" "服务器异常"通过HTTP请求发送报警通知

1. 参数说明:函数接收三个参数,分别是PU使用率阈值、内存使用率阈值以及监控间隔时间(秒)。

2. 循环监控:使用 while true 循环不断监控系统资源。通过 top 和 free 命令获取 CPU 和内存使用率,使用 bc 命令进行浮点数比较。

3. 报警处理:当使用率超过阈值时,调用send alert 函数发送报警信息。

2:函数变量的作用范围

            在 Shell脚本中函数的执行并不会开启一个新的子 Shell,而是仅在当前定义的 shell环境中有效。如果Shell脚本中的变量没有经过特殊设定,默认在整个脚本中都是有效的。

1.变量作用范围类型

变量类型声明方式作用范围生命周期示例
全局变量默认声明(无修饰符)整个脚本(包括函数内外)从声明到脚本结束var="value"
局部变量使用local关键字仅在当前函数内有效函数执行期间local var="value"
环境变量使用export命令当前脚本及其子进程直到进程结束export VAR="value"
位置参数$1$2, ..., $n函数内部(会覆盖脚本参数)函数执行期间echo "First arg:

2.作用范围规则表格

规则说明示例
1. 函数内未声明直接使用的变量会访问或修改全局变量function f1() { var=10; }
var=5; f1; echo $var # 输出10
2. 使用local声明的变量只在函数内有效,不影响外部同名变量function f2() { local var=20; }
var=5; f2; echo $var # 输出5
3. 函数参数变量$1-$n只在函数内有效,不影响脚本参数function f3() { echo $1; }
f3 "hello" # 输出hello
4. 函数内修改环境变量需要使用exportdeclare -xfunction f4() { export VAR=30; }
5. 子shell中的变量不会影响父shell的变量var=5; (var=10); echo $var # 输出5
6. 函数返回值通过return返回状态码,通过echo返回数据function f5() { echo "data"; return 0; }

           在编写脚本时,有时需要将变量的值限定在函数内部,可以通过内置命令 local 来实现。函数内部变量的使用,可以避免函数内外同时出现同名变量对脚本结果的影响。local 命令的使用如下所示。

        上述脚本中,myfun 函数内部使用了 local 命令设置变量 i,其作用是将变量 i 限定在函数内部。myfun 函数外部同样定义了变量 i,内部变量 i 和全局变量 i 互不影响。脚本执行时先调用了函数myfun,函数内部变量i为 8,所以输出结果是 8。调用完函数之后,给变量i 赋值为 9,再打印外部变量 i,所以又输出 9。

3:函数的参数 

函数的参数的用法如下。
函数名称 参数 1 参数 2 参数 3......

1.函数参数基础

参数变量描述示例
$1 - $n位置参数,表示第1到第n个参数echo "第一个参数: $1"
$0函数名(在大多数Shell中),脚本名(在某些Shell中)echo "函数名: $0"
$#传递给函数的参数个数echo "参数个数: $#"
$@所有参数的列表,每个参数作为独立字符串for arg in "$@"; do echo "$arg"; done
$*所有参数的列表,合并为单个字符串echo "所有参数: $*"

2.参数传递模式对比

传递方式语法特点适用场景
位置参数func arg1 arg2简单直接少量简单参数
选项参数func -a val1 -b val2可读性强,顺序灵活复杂参数设置
数组展开func "${array[@]}"传递数组内容需要处理数组时
Here Stringfunc <<< "$data"传递字符串简单字符串输入
文件描述符func <(command)传递命令输出需要处理命令输出时

          在使用函数参数时,函数名称在前参数在后,函数名和参数之间用空格分隔,可以有多个参数,参数使用$1、$2、$3.的方式表示。以此类推,从第 10 个参数开始,调用方法为${10},不加大括号无法调用成功。下面是函数参数的一个简单应用。

第一个参数是写日志的目标文件

第二个参数是日志信息

整个脚本实现将日志信息写入目标文件内的目的。

4:递归函数

          Shell也可以实现递归函数,就是可以调用自己本身的函数。在 Linux 系统上编写 shell脚本的时候,经常需要递归遍历系统的目录,列出目录下的文件和目录,逐层递归列出,并对这些层级关系进行展示。具体的实现过程如下所示。

函数 list_files 的第一个参数是列举的目录名,第二个参数是调整的空间。

         递归函数是指调用自身的函数,在 Shell 脚本中也可以实现递归功能。

1.递归函数关键要素

要素说明示例
终止条件必须存在明确的递归结束条件if [ $n -eq 0 ]; then return; fi
参数变化每次递归调用参数必须向终止条件靠近factorial $((n-1))
局部变量必须使用local声明变量,避免递归间干扰local result=$(recursive_func)
返回值可通过return返回状态码或echo返回数据echo $result 或 return 0
栈深度Shell有递归深度限制(通常几百层)ulimit -s查看栈大小

2.递归优化技巧

技巧说明示例
尾递归将递归调用放在函数最后,减少栈使用tail_recursive $((n-1))
记忆化缓存已计算结果,避免重复计算使用关联数组存储中间结果
迭代替代对深度递归考虑用循环改写for/while循环替代递归
尾调用优化确保递归调用是最后一步操作return $(recursive_call)

二:Shell数组

                在 shell脚本中,数组是一种常见的数据结构,主要的应用场景包括:获取数组长度、获取元素长度、遍历元素、元素切片、元素替换、元素删除等等。shell中的数组与 Java、c、Python 不同,只有一维数组,没有二维数组。数组元素的大小与限制,也不需要事先定义。shell数组用括号()来表示,元素用空格分隔,元素的下标与大部分编程语言类似从0开始。

数组常用定义方法包括以下几种。
方法一:
数组名=(value0 value1 value2...)
方法二:
数组名=([0]=value[1]=value[2]=value ...)
方法三:
列表名=”value0 value1 value2 ...数组名=($列表名)
方法四:
数组名[0]=”value”数组名[2]=”value’数组名[1]=”value”

1. 索引数组(普通数组)

操作语法示例
声明数组array=(value1 value2 value3)fruits=("apple" "banana" "cherry")
设置元素array[index]=valuefruits[0]="apricot"
获取元素${array[index]}echo ${fruits[1]} # 输出 banana
获取所有元素${array[@]} 或 ${array[*]}echo ${fruits[@]}
数组长度${#array[@]}echo ${#fruits[@]} # 输出元素个数
获取索引${!array[@]}echo ${!fruits[@]} # 输出 0 1 2

2. 关联数组(键值对)

操作语法示例
声明关联数组declare -A arraydeclare -A user
设置元素array[key]=valueuser[name]="Alice"
获取元素${array[key]}echo ${user[name]}
获取所有值${array[@]}echo ${user[@]}
获取所有键${!array[@]}echo ${!user[@]}
数组长度${#array[@]}echo ${#user[@]}

下面通过具体的示例掌握数组的基本使用方法。

(1)获取数组长度

                    在 Shell编程中,数组是一种用于存储多个值的数据结构。获取数组长度(即数组中元素的个数)在很多场景下都非常有用,比如进行数组遍历、数据处理等。普通数组是 shell中最常见的数组类型,其下标是从 0开始的连续整数。获取普通数组长度可以使用 ${#数组名[@]}或 ${#数组名[*]} 这两种语法,它们的效果是一样的。

(2) 读取某下标赋值

              在 shell编程里,你可以读取数组中指定下标的元素,也可以给指定下标的数组元素赋值。普通数组是 Shell中最常用的数组类型,其下标是从0开始的整数。

(3)数组遍历

              在 Shell编程里,数组遍历指的是按顺序访问数组中的每一个元素,并对这些元素执行特定操作的过程。这在处理一组相关数据时非常有用,比如批量处理文件、统计数据等。

(4)数组切片

              数组切片是一种操作,用于从数组中提取出一部分连续的元素,形成一个新的数组。这个操作在很多编程语言中都有支持,不过不同语言实现数组切片的语法和方式会有所差异。在Bash 脚本里,也可以进行数组切片操作。语法是 ${array[@]:start:length},其中 start 是开始的索引,length 是要提取的元素个数。

将数组切片之后,返回的是字符串,以空格作为分隔符。

(5)数组替换

(6)数组删除

三: Shell脚本调试

               在 shell脚本开发中,经常碰到一些规范方面的问题,例如忘了使用引号或在 if 语句末尾处忘记加 fi 结束。要注意把复杂的脚本简单化,要思路清晰,并且分段实现。当执行脚本时出现错误后,不要只看那些提示的错误行,而是要观察整个相关的代码段。

               为避免编写的脚本出错,除了在编写脚本时注意书写规范,排除语法错误,更重要的是利用调试脚本工具来调试脚本。echo 命令是最有用的调试脚本工具之一,一般在可能出现问题的脚本中加入 echo 命令,采用的是分段排查的方式。

                除了 echo 命令之外,bash shell也有相应参数可以调试脚本。使用 bash 命令参数调试,命令的语法为:

sh    [-nvx]    脚本名

常用参数的具体含义为:

参数含义
-n不会执行该脚本,仅查询脚本语法是否有问题,如果没有语法问题就不显示任何内容,如果有问题会提示报错。
-v在执行脚本时,先将脚本的内容输出到屏幕上然后执行脚本,如果有错误,也会给出错误提示。
-x将执行的脚本内容输出到屏幕上,这个是对调试很有用的参数。

1.基础调试方法

方法命令/选项说明示例
打印调试echo/printf输出变量和状态信息echo "变量值: $var"
执行跟踪set -x/set +x显示执行的每条命令及其参数set -x
your_script.sh
set +x
详细模式set -v/set +v显示读取的输入行set -v
your_script.sh
set +v
严格模式set -euo pipefail组合严格检查选项set -euo pipefail
语法检查bash -n只检查语法不执行bash -n script.sh

2.调试工具

工具用途示例
shellcheck静态分析工具shellcheck script.sh
bashdbBash调试器bashdb script.sh
vscode/bash插件集成开发环境调试配置launch.json
xtrace增强的跟踪输出PS4='+${BASH_SOURCE}:${LINENO}: ' bash -x script.sh

3.常见错误排查表

错误类型排查方法解决方案
语法错误bash -n script.sh检查括号、引号匹配
变量未定义set -u确保变量已初始化
命令失败echo $?检查命令返回值
权限问题ls -l添加执行权限 chmod +x
文件不存在test -f file检查文件路径
管道错误set -o pipefail检查管道中所有命令
参数错误echo "$@"验证参数数量 ${#@}

4.性能调试

方法说明示例
time测量命令执行时间time your_script.sh
pv监控管道数据流cat bigfile.log | pv | grep "error"
strace跟踪系统调用strace -f -o trace.log bash script.sh
perf性能分析工具perf stat bash script.sh

当脚本文件较长时,可以使用 set 命令指定调试一段脚本。

本章总结

函数:函数是 shell脚本中组织代码、提高复用性的重要手段。通过将一系列指令封装成函数,可以避免代码重复,使脚本更加简洁易读。函数的定义与调用简单直观,支持参数传递和递归调用,为模块化开发提供了便利。

数组:作为 Shell脚本中的一种重要数据结构,数组支持存储多个值,并提供了丰富的操作方法,如获取数组长度、读取和赋值、遍历、切片、替换和删除元素等。这些功能在处理一组相关数据时非常有用。

调试与测试:在 Shell脚本编写过程中,调试与测试是确保脚本正确运行的关键环节。通过使用 echo命令和 bash 的调试参数(-n、-v、-x),可以方便地定位和解决问题,提高脚本的稳健性。

http://www.xdnf.cn/news/360019.html

相关文章:

  • CSS flex:1
  • 101 alpha——8 学习
  • PostgreSQL冻结过程
  • Linux 学习笔记2
  • LeetCode:101、对称二叉树
  • STM32GPIO输入实战-key按键easy_button库移植
  • flex 还是 inline-flex?实际开发中应该怎么选?
  • 【Python 列表(List)】
  • 传统数据展示 vs 可视化:谁更打动人心?
  • 第十七节:图像梯度与边缘检测-Sobel 算子
  • Python函数:从基础到进阶的完整指南
  • 2006-2023年各省研发投入强度数据/研究与试验发展(RD)经费投入强度数据(无缺失)
  • 【大语言模型ChatGPT4/4o 】“AI大模型+”多技术融合:赋能自然科学暨ChatGPT在地学、GIS、气象、农业、生态与环境领域中的应用
  • Python基础学习-Day20
  • Transformer编码器+SHAP分析,模型可解释创新表达!
  • 星云智控:物联网时代的设备守护者——卓伊凡详解物联网监控革命-优雅草卓伊凡
  • 2021-11-15 C++下一个生日天数
  • 【计算机视觉】OpenCV实战项目: opencv-text-deskew:实时文本图像校正
  • Bitcoin跨链协议Clementine的技术解析:重构DeFi生态的信任边界
  • .Net HttpClient 概述
  • CTF-DAY11
  • ClickHouse多表join的性能优化:原理与源码详解
  • WebSocket:实时通信的新时代
  • List<T>中每次取固定长度的数据
  • 报错 | vitest中,vue中使用jsx语法,报错:ReferenceError: React is not defined
  • 图上思维:基于知识图的大型语言模型的深层可靠推理
  • YOLOv8 优化:基于 Damo-YOLO 与 DyHead 检测头融合的创新研究
  • Android Framework学习四:init进程实现
  • 矩阵分解——Cholesky分解,LU分解,LDLT分解
  • 华为5.7机考第一题充电桩问题Java代码实现