【网络运维】Shell脚本编程:函数
Shell脚本编程:函数
Shell 函数介绍
在 Shell 编程中,函数是一种非常重要的代码组织和复用机制。我们可以将函数理解为一种“高级别名”,它不仅能够简化代码,还能提高程序的可读性和可维护性。
与 alias
类似,函数可以封装常用操作:
# 使用 alias 简化命令
alias ll='ls -l --color=auto'
ll /home/
但函数比 alias 更强大,它能够:
- 减少代码重复,提升开发效率
- 增强程序可读性和可管理性
- 实现程序功能模块化,提高可移植性
Shell 函数的语法
Shell 函数有三种常见的定义方式:
标准写法
function 函数名 () {指令...return n
}
简化写法1:省略小括号
function 函数名 {指令...return n
}
简化写法2:省略 function 关键字
函数名 () {指令...return n
}
Shell 函数的执行
1. 执行不带参数的函数
直接输入函数名即可(不需要小括号):
函数名
重要说明:
- 函数定义必须在调用之前
- Shell 执行优先级:系统别名 → 函数 → 系统命令 → 可执行文件
- 函数内使用
return
退出函数,exit
退出整个脚本 - 使用
local
定义局部变量,这些变量在函数外不可见 - 独立函数文件需要使用
source
或.
加载
2. 执行带参数的函数
函数名 参数1 参数2
参数说明:
- 使用
$1
,$2
…$n
获取参数 $#
表示参数个数,$*
和$@
表示所有参数$0
仍然是父脚本的名称- 函数参数会临时覆盖父脚本的参数
Shell 函数实践示例
示例1:基础 hello 函数
#!/bin/bash
# 定义函数
function hello () {echo "Hello World !"
}
# 调用函数
hello
执行结果:
Hello World !
错误示例:先调用后定义
#!/bin/bash
hello # 此时函数还未定义,会报错
function hello () {echo "Hello World !"
}
执行结果:
fun2.sh: line 2: hello: command not found
关键点:函数必须先定义后调用
示例2:调用外部函数文件
创建函数库文件:
# 创建名为 mylib 的函数库
cat >> mylib << 'EOF'
function hello () {echo "Hello World !"
}
EOF
在主脚本中调用:
#!/bin/bash
# 检查函数库是否存在并可读
if [ -r mylib ]; thensource mylib # 加载函数库
elseecho "mylib does not exist or is not readable"exit 1
fihello # 调用函数
执行结果:
Hello World !
关键点:使用
source
或.
命令加载外部函数定义
示例3:带参数的函数
#!/bin/bash
function print () {# 根据参数值输出不同颜色的文本if [ "$1" == "PASS" ]; thenecho -e '\033[1;32mPASS\033[0;39m' # 绿色elif [ "$1" == "FAIL" ]; thenecho -e '\033[1;31mFAIL\033[0;39m' # 红色elif [ "$1" == "DONE" ]; thenecho -e '\033[1;35mDONE\033[0;39m' # 紫色elseecho "Usage: print PASS|FAIL|DONE"fi
}# 获取用户输入
read -p "请输入你想要打印的内容:" str
print $str # 调用函数并传递参数
测试结果:
# 运行脚本
请输入你想要打印的内容:PASS
PASS(绿色显示)请输入你想要打印的内容:hello
Usage: print PASS|FAIL|DONE
关键点:函数内部通过
$1
获取第一个参数
示例4:函数参数与脚本参数
#!/bin/bash
function print () {if [ "$1" == "PASS" ]; thenecho -e '\033[1;32mPASS\033[0;39m'elif [ "$1" == "FAIL" ]; thenecho -e '\033[1;31mFAIL\033[0;39m'elif [ "$1" == "DONE" ]; thenecho -e '\033[1;35mDONE\033[0;39m'else# 注意:这里的 $0 仍然是脚本名,不是函数名echo "Usage: $0 PASS|FAIL|DONE"fi
}str=$2 # 获取脚本的第二个参数
print $str # 传递给函数
测试结果:
bash fun5.sh PASS FAIL
FAIL # 显示的是第二个参数FAIL,而不是第一个参数PASSbash fun5.sh
Usage: fun5.sh PASS|FAIL|DONE # $0 显示脚本名
关键点:函数参数会覆盖脚本参数,但
$0
始终指向脚本名称
函数实战:URL检测脚本
#!/bin/bash
# 使用方法提示函数
function usage () {echo "usage: $0 url"exit 1
}# URL检测函数
function check_url () {# 使用wget检测URL可访问性wget --spider -q -o /dev/null --tries=1 -T 5 $1# 根据返回状态码判断结果[ $? -eq 0 ] && echo "$1 is accessible" || echo "$1 is not accessible"
}# 主函数
function main () {# 检查参数个数是否正确[ $# -ne 1 ] && usagecheck_url $1
}# 执行主函数,传递所有参数
main $*
测试结果:
bash check_url.sh www.baidu.com
www.baidu.com is accessiblebash check_url.sh www.nonexistent.com
www.nonexistent.com is not accessible
关键点:模块化设计,分离功能逻辑,提高代码可维护性
函数的递归调用
示例1:计算1到n的累加和
#!/bin/bash
function sum_out() {if [ $1 -eq 1 ]; thensum=1 # 递归基线条件else# 递归调用:n + (n-1)的和sum=$[ $1 + $(sum_out $[ $1 - 1 ]) ]fiecho $sum
}read -p "输入一个你想计算和的整数:" num
sum_out $num
示例2:计算n的阶乘
#!/bin/bash
function fact_out() {if [ $1 -eq 1 ]; thensum=1 # 递归基线条件else# 递归调用:n * (n-1)的阶乘sum=$[ $1 * $(fact_out $[ $1 - 1 ]) ]fiecho $sum
}read -p "输入一个你想计算阶乘的整数:" num
fact_out $num
示例3:fork炸弹(⚠️危险示例,请勿实际运行)
:(){ :|:& };:
代码解析:
:()
- 定义函数名为冒号{ :|:& }
- 函数体:递归调用自身两次,并通过管道并行执行;
- 命令分隔符:
- 调用函数,启动炸弹
原理:无限递归创建进程,耗尽系统资源
防范措施:限制用户进程数
ulimit -u 100 # 限制用户最多100个进程