Shell脚本函数#
函数是Shell脚本中组织代码、提高复用性和可维护性的重要机制。本教程将详细介绍Shell中的函数定义、参数传递、返回值处理、作用域管理等内容,从基础的函数定义到高级的函数库和模块化设计。
入门#
基本函数定义#
在Shell中,函数可以通过两种方式定义:使用function关键字或直接定义函数名。
# 使用function关键字定义函数
function hello {
echo "Hello, World!"
}
# 直接定义函数名
hello() {
echo "Hello, World!"
}
# 调用函数
hello
# 带参数的函数
greet() {
echo "Hello, $1!"
}
# 调用带参数的函数
greet "John"函数参数#
Shell函数通过位置参数($1, $2, 等)接收参数,与脚本参数类似。
# 带多个参数的函数
add() {
echo "参数1: $1"
echo "参数2: $2"
sum=$(( $1 + $2 ))
echo "和: $sum"
}
# 调用带多个参数的函数
add 10 20
# 处理任意数量的参数
sum_all() {
local sum=0
for num in "$@"; do
sum=$(( sum + num ))
done
echo "总和: $sum"
}
# 调用带任意数量参数的函数
sum_all 1 2 3 4 5函数返回值#
Shell函数通过return语句返回退出状态码(0-255),或通过输出返回数据。
# 使用return返回退出状态码
is_positive() {
if [ $1 -gt 0 ]; then
return 0 # 成功
else
return 1 # 失败
fi
}
# 检查返回值
is_positive 5
if [ $? -eq 0 ]; then
echo "是正数"
else
echo "不是正数"
fi
# 通过输出返回数据
calculate_sum() {
local sum=0
for num in "$@"; do
sum=$(( sum + num ))
done
echo $sum # 输出结果
}
# 捕获函数输出
result=$(calculate_sum 1 2 3 4 5)
echo "计算结果: $result"中级#
函数作用域#
Shell中的变量默认是全局的,使用local关键字可以定义局部变量。
# 全局变量
GLOBAL_VAR="全局变量"
# 函数中的局部变量
my_function() {
local LOCAL_VAR="局部变量"
GLOBAL_VAR="修改后的全局变量"
echo "函数内 - 全局变量: $GLOBAL_VAR"
echo "函数内 - 局部变量: $LOCAL_VAR"
}
# 调用函数
my_function
# 函数外访问
echo "函数外 - 全局变量: $GLOBAL_VAR"
echo "函数外 - 局部变量: $LOCAL_VAR" # 不会输出,因为局部变量只在函数内可见函数库#
将多个相关函数组织到一个文件中,形成函数库,便于在多个脚本中复用。
# 创建函数库文件: utils.sh
#!/bin/bash
# 输出带时间戳的日志
log() {
local level=$1
shift
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [$level] $@"
}
# 检查文件是否存在且可读
check_file() {
local file=$1
if [ -f "$file" ] && [ -r "$file" ]; then
return 0
else
return 1
fi
}
# 计算文件大小(字节)
file_size() {
local file=$1
if check_file "$file"; then
stat -c "%s" "$file"
else
echo 0
fi
}# 在脚本中使用函数库
#!/bin/bash
# 导入函数库
source utils.sh
# 使用函数库中的函数
log "INFO" "开始执行脚本"
if check_file "data.txt"; then
size=$(file_size "data.txt")
log "INFO" "文件大小: $size 字节"
else
log "ERROR" "文件不存在或不可读"
fi
log "INFO" "脚本执行完成"高级#
高级函数特性#
递归函数#
# 递归计算阶乘
factorial() {
local n=$1
if [ $n -le 1 ]; then
echo 1
else
local prev=$(factorial $((n-1)))
echo $((n * prev))
fi
}
# 调用递归函数
echo "5! = $(factorial 5)"
# 递归遍历目录
list_dir() {
local dir=$1
local indent=$2
for item in "$dir"/*; do
if [ -d "$item" ]; then
echo "${indent}$(basename "$item")/"
list_dir "$item" "${indent} "
elif [ -f "$item" ]; then
echo "${indent}$(basename "$item")"
fi
done
}
# 调用目录遍历函数
list_dir "." ""函数参数处理#
# 处理选项和参数
parse_args() {
local verbose=0
local output="output.txt"
# 解析选项
while getopts "vo:" opt; do
case $opt in
v)
verbose=1
;;
o)
output=$OPTARG
;;
*)
echo "无效选项: $opt"
return 1
;;
esac
done
# 移除选项,剩下的是位置参数
shift $((OPTIND - 1))
echo "verbose: $verbose"
echo "output: $output"
echo "位置参数: $@"
}
# 调用带选项的函数
parse_args -v -o result.txt file1.txt file2.txt函数返回值高级处理#
# 返回复杂数据(使用数组)
get_files() {
local dir=$1
local pattern=$2
local files=()
for file in "$dir"/$pattern; do
if [ -f "$file" ]; then
files+=("$file")
fi
done
# 通过输出返回数组元素
printf "%s\n" "${files[@]}"
}
# 捕获返回的数组
files=($(get_files "." "*.txt"))
echo "找到的文件:"
for file in "${files[@]}"; do
echo "- $file"
done
# 使用关联数组返回键值对
get_system_info() {
local info=()
info["os"]=$(uname -s)
info["version"]=$(uname -r)
info["arch"]=$(uname -m)
# 通过输出返回键值对
for key in "${!info[@]}"; do
echo "$key=${info[$key]}"
done
}
# 捕获返回的键值对
declare -A system_info
while IFS="=" read -r key value; do
system_info["$key"]="$value"
done < <(get_system_info)
echo "系统信息:"
for key in "${!system_info[@]}"; do
echo "$key: ${system_info[$key]}"
done函数作用域和变量管理#
# 全局变量和局部变量
global_var="全局"
my_func() {
local local_var="局部"
global_var="修改后的全局"
echo "函数内: global_var=$global_var, local_var=$local_var"
}
my_func
echo "函数外: global_var=$global_var, local_var=$local_var"
# 静态变量(使用局部变量模拟)
counter() {
# 使用local -r确保变量只初始化一次
local -r init=0
# 使用文件或环境变量存储状态
if [ -z "$COUNTER" ]; then
export COUNTER=$init
fi
COUNTER=$((COUNTER + 1))
echo $COUNTER
}
# 调用计数器函数
echo "计数: $(counter)"
echo "计数: $(counter)"
echo "计数: $(counter)"
# 清除计数器
unset COUNTER大师#
函数库和模块化设计#
创建可重用的函数库#
# 创建完整的函数库: lib.sh
#!/bin/bash
# 颜色定义
readonly COLOR_RESET="\033[0m"
readonly COLOR_RED="\033[31m"
readonly COLOR_GREEN="\033[32m"
readonly COLOR_YELLOW="\033[33m"
readonly COLOR_BLUE="\033[34m"
# 日志函数
log_info() {
echo -e "${COLOR_BLUE}[INFO]${COLOR_RESET} $@"
}
log_success() {
echo -e "${COLOR_GREEN}[SUCCESS]${COLOR_RESET} $@"
}
log_warning() {
echo -e "${COLOR_YELLOW}[WARNING]${COLOR_RESET} $@"
}
log_error() {
echo -e "${COLOR_RED}[ERROR]${COLOR_RESET} $@"
}
# 文件操作函数
file_exists() {
[ -f "$1" ]
}
dir_exists() {
[ -d "$1" ]
}
ensure_dir() {
local dir=$1
if ! dir_exists "$dir"; then
log_info "创建目录: $dir"
mkdir -p "$dir"
fi
}
# 系统函数
is_root() {
[ $(id -u) -eq 0 ]
}
get_os() {
uname -s
}
# 网络函数
is_connected() {
ping -c 1 -W 2 google.com > /dev/null 2>&1
}
# 字符串函数
trim() {
local var="$1"
var="${var#"${var%%[![:space:]]*}"}" # 去除前导空格
var="${var%"${var##*[![:space:]]}"}" # 去除尾随空格
echo "$var"
}
# 数组函数
array_contains() {
local item=$1
shift
local array=($@)
for element in "${array[@]}"; do
if [ "$element" == "$item" ]; then
return 0
fi
done
return 1
}模块化导入#
# 主脚本: main.sh
#!/bin/bash
# 导入函数库
source lib.sh
# 使用函数库中的函数
log_info "开始执行主脚本"
# 检查权限
if ! is_root; then
log_warning "非root用户执行"
fi
# 检查网络连接
if is_connected; then
log_success "网络连接正常"
else
log_error "网络连接失败"
fi
# 检查文件
if file_exists "data.txt"; then
log_info "数据文件存在"
else
log_warning "数据文件不存在"
fi
# 确保输出目录存在
ensure_dir "output"
log_success "脚本执行完成"高级函数技巧#
函数柯里化#
# 柯里化函数示例
create_greeter() {
local greeting=$1
# 返回一个函数
greeter() {
echo "$greeting, $1!"
}
# 通过输出返回函数定义
declare -f greeter
}
# 创建不同的问候函数
greet_hello=$(create_greeter "Hello")
greet_hi=$(create_greeter "Hi")
# 执行返回的函数
eval "$greet_hello" "John"
eval "$greet_hi" "Jane"函数装饰器#
# 函数装饰器示例 - 计时
with_timer() {
local func=$1
# 返回装饰后的函数
decorated() {
local start=$(date +%s.%N)
"$func" "$@"
local end=$(date +%s.%N)
local duration=$(echo "$end - $start" | bc)
echo "执行时间: $duration 秒"
}
declare -f decorated
}
# 原始函数
slow_func() {
echo "执行缓慢操作..."
sleep 1
echo "操作完成"
}
# 装饰函数
timed_func=$(with_timer slow_func)
# 执行装饰后的函数
eval "$timed_func"惰性加载函数#
# 惰性加载函数
lazy_load() {
local func_name=$1
local loader=$2
# 临时函数,用于加载实际函数
"$func_name"() {
# 加载实际函数
eval "$loader"
# 移除临时函数定义
unset -f "$func_name"
# 重新调用函数(现在是实际函数)
"$func_name" "$@"
}
}
# 定义惰性加载的函数
lazy_load expensive_func "
expensive_func() {
echo "这是实际的昂贵函数"
# 这里可以包含复杂的初始化代码
}
"
# 首次调用时会加载
echo "首次调用:"
expensive_func
# 后续调用直接使用加载后的函数
echo "后续调用:"
expensive_func无敌#
函数库设计模式#
命名空间#
# 使用前缀创建命名空间
# 网络相关函数
net_ping() {
ping -c 1 "$1"
}
net_traceroute() {
traceroute "$1"
}
# 文件相关函数
file_copy() {
cp "$1" "$2"
}
file_move() {
mv "$1" "$2"
}
# 调用带命名空间的函数
net_ping google.com
file_copy source.txt dest.txt插件系统#
# 插件系统示例
# 插件目录
PLUGIN_DIR="./plugins"
# 加载插件
load_plugins() {
for plugin in "$PLUGIN_DIR"/*.sh; do
if [ -f "$plugin" ]; then
echo "加载插件: $(basename "$plugin")"
source "$plugin"
fi
done
}
# 定义插件接口
register_command() {
local name=$1
local func=$2
local description=$3
commands["$name"]="$func"
command_descriptions["$name"]="$description"
}
# 初始化命令数组
declare -A commands
declare -A command_descriptions
# 加载插件
load_plugins
# 显示可用命令
echo "可用命令:"
for cmd in "${!commands[@]}"; do
echo "- $cmd: ${command_descriptions[$cmd]}"
done
# 执行命令
if [ -n "$1" ] && [ -n "${commands[$1]}" ]; then
shift
"${commands[$1]}" "$@"
else
echo "无效命令"
fi插件示例#
# 插件: plugins/hello.sh
register_command "hello" "plugin_hello" "问候命令"
plugin_hello() {
echo "Hello, $1!"
}
# 插件: plugins/system.sh
register_command "system" "plugin_system" "系统信息命令"
plugin_system() {
uname -a
}实用脚本示例#
系统管理工具#
#!/bin/bash
# 系统管理工具函数库
# 检查服务状态
check_service() {
local service=$1
if systemctl is-active --quiet "$service"; then
echo "服务 $service 运行正常"
return 0
else
echo "服务 $service 未运行"
return 1
fi
}
# 管理服务
manage_service() {
local action=$1
local service=$2
case $action in
start)
sudo systemctl start "$service"
;;
stop)
sudo systemctl stop "$service"
;;
restart)
sudo systemctl restart "$service"
;;
status)
check_service "$service"
;;
*)
echo "无效操作: $action"
return 1
;;
esac
}
# 磁盘管理
check_disk() {
local mount_point=${1:-/}
local usage=$(df -h "$mount_point" | tail -n 1 | awk '{print $5}' | sed 's/%//')
echo "挂载点 $mount_point 使用率: $usage%"
if [ $usage -gt 90 ]; then
echo "警告: 磁盘空间不足"
return 1
elif [ $usage -gt 70 ]; then
echo "注意: 磁盘空间紧张"
return 0
else
echo "磁盘空间正常"
return 0
fi
}
# 内存管理
check_memory() {
local total=$(free -m | grep Mem | awk '{print $2}')
local used=$(free -m | grep Mem | awk '{print $3}')
local usage=$(( used * 100 / total ))
echo "内存总量: ${total}MB"
echo "已用内存: ${used}MB"
echo "内存使用率: ${usage}%"
if [ $usage -gt 80 ]; then
echo "警告: 内存使用率过高"
return 1
else
echo "内存使用正常"
return 0
fi
}
# 主菜单
show_menu() {
echo "===== 系统管理工具 ====="
echo "1. 检查服务状态"
echo "2. 管理服务"
echo "3. 检查磁盘空间"
echo "4. 检查内存使用"
echo "5. 退出"
echo "===================="
}
# 主循环
while true; do
show_menu
read -p "请选择: " choice
case $choice in
1)
read -p "输入服务名: " service
check_service "$service"
;;
2)
read -p "输入操作(start/stop/restart/status): " action
read -p "输入服务名: " service
manage_service "$action" "$service"
;;
3)
read -p "输入挂载点(默认/): " mount_point
check_disk "${mount_point:-/}"
;;
4)
check_memory
;;
5)
echo "再见!"
exit 0
;;
*)
echo "无效选择"
;;
esac
echo "按Enter键继续..."
read
done总结#
函数是Shell脚本中组织代码、提高复用性和可维护性的核心机制。通过掌握函数定义、参数传递、返回值处理、作用域管理等技巧,您可以创建更加模块化、可维护的Shell脚本。从简单的工具函数到复杂的函数库和插件系统,函数为Shell脚本提供了强大的组织能力。
随着您对Shell函数的理解不断深入,您将能够编写更加专业、高效的Shell脚本,处理各种复杂的系统管理和自动化任务。记住,良好的函数设计是构建高质量Shell脚本的基础。