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

五、shell脚本--函数与脚本结构:搭积木,让脚本更有条理

随着我们的脚本越来越长、越来越复杂,直接把所有命令堆在一起会变得难以阅读维护。这时候,函数 (Function) 就派上大用场了!

函数就像一个可以重复使用的代码块,你可以给它起个名字,然后在脚本的任何地方通过名字来调用它执行。这有点像我们生活中的“工具”或者“食谱”:

  • 避免重复: 如果有一段代码需要反复使用,把它写成函数,就不用每次都复制粘贴了,干净利落✨。
  • 提高可读性: 把特定的功能封装在函数里,主脚本的逻辑就会更清晰,一眼就能看出它在干什么。
  • 方便维护: 如果某个功能需要修改,只需要改动对应的函数内部代码就行了,牵一发而动全身的风险大大降低🔧。

一、 函数的定义与调用:创建和使用你的“工具”

1.定义函数的语法

在 Shell 脚本里定义函数主要有两种方式,它们基本是等价的:

方式一 (带 function 关键字):

function 函数名 {# 函数内部的命令...命令1命令2# ...
}

方式二 (更常用,类似 C 语言):

函数名() {# 函数内部的命令...命令1命令2# ...
}

注意: 函数名后面的圆括号 ()花括号 {} 是必须的。花括号 {} 里面就是函数要执行的代码体。函数体里的命令并不会在定义时立刻执行。

示例:定义一个简单的问候函数

#!/bin/bash# 使用方式二定义函数
print_hello() {echo "你好,欢迎使用 Shell 函数!"
}
2.调用函数

定义好函数之后,想要执行它里面的代码,只需要在脚本中像调用普通命令一样,直接写函数名就行了。

示例:调用上面定义的函数

#!/bin/bash# 先定义函数
print_hello() {echo "你好,欢迎使用 Shell 函数!"
}# 然后调用它
echo "准备调用函数..."
print_hello # 直接写函数名来调用
echo "函数调用完毕。"

运行这个脚本,你就会看到 你好,欢迎使用 Shell 函数! 这句话被打印出来。

3.函数参数的传递与使用:给“工具”加点料 🔧🔩

函数不仅能执行固定操作,还能接收外部传来的数据,这些数据就叫做参数 (Parameters)。函数内部获取参数的方式和脚本获取命令行参数完全一样

  • $1: 函数接收到的第一个参数。
  • $2: 函数接收到的第二个参数。
  • … 以此类推 …
  • $#: 传递给函数的参数总个数
  • $*: 所有参数组成的单一字符串
  • $@: 所有参数组成的独立字符串列表 (推荐用 "$@" 来处理)。

调用函数时传递参数: 直接在函数名后面跟上空格隔开的参数值就行了。

示例:定义并调用一个带参数的问候函数

#!/bin/bash# 定义一个接收名字的函数
greet_user() {# $1 代表传进来的第一个参数 (名字)local user_name=$1 # 把参数存到局部变量里是个好习惯echo "你好, $user_name!很高兴见到你。👋"echo "这次调用一共传入了 $# 个参数。"
}# 调用函数,并传递参数
greet_user "张三" # 把 "张三" 作为 $1 传给函数
greet_user "李四" "王五" # 把 "李四" 作为 $1, "王五" 作为 $2 传进去
4.函数的返回值:告诉外面“我干得怎么样” 🤔✅❌

函数执行完后,怎么告诉调用它的地方“任务完成得如何”或者“结果是什么”呢?Shell 函数主要通过两种方式返回信息:

4.1.退出状态码 (Exit Status): 使用 return 命令 (0-255)

  • 作用: 主要用来表示函数执行的成功或失败状态,就像普通命令的退出码一样。

  • 命令: return N,其中 N 是一个0 到 255 之间的整数。

  • 约定: return 0 通常表示成功 ✅,非零值 (1-255) 表示失败或某种错误状态 ❌。

  • 如何获取: 在函数调用之后立刻检查特殊变量 $?,它的值就是 return 命令指定的那个数字 N。如果不使用 return$? 的值是函数体中最后一条命令的退出状态码。

  • 注意: return 主要用于传递状态信息不适合用来传递复杂的计算结果或长字符串。

    示例:检查文件是否存在的函数

#!/bin/bash
# 定义函数,检查文件是否存在
check_file_exists() {local filename=$1if [ -f "$filename" ]; thenecho "调试信息:文件 '$filename' 存在。"return 0 # 文件存在,返回 0 (成功)elseecho "调试信息:文件 '$filename' 不存在。"return 1 # 文件不存在,返回 1 (失败)fi
}# 调用函数并检查返回值 $?
target_file="/etc/passwd"
check_file_exists "$target_file"
status=$? # 立刻保存 $? 的值!echo "检查 $target_file 的退出状态码是: $status"
if [ $status -eq 0 ]; thenecho "检查结果:文件确实存在。"
elseecho "检查结果:文件不存在或不是普通文件。"
fi# 再试一个不存在的文件
check_file_exists "/no/such/file"
status=$?
echo "检查 /no/such/file 的退出状态码是: $status"
# ... 后续判断同上 ...

4.2.标准输出 (stdout): 使用 echo 或其他打印命令 (传递数据)

  • 作用: 这是函数向外部传递计算结果、字符串或其他数据最常用方式。
  • 方法: 在函数内部,使用 echo (或其他会输出到屏幕的命令) 打印你想“返回”的数据。
  • 如何获取: 在调用函数的地方,使用命令替换 $(...) (或者老式的反引号 `...`) 来捕获函数的标准输出,并将其赋值给一个变量。

示例:一个返回问候语字符串的函数

#!/bin/bash
# 定义函数,它会"返回"一个拼接好的字符串
generate_greeting() {local name=$1local time_period="早上" # 简化处理,总是早上好# 注意:这里用 echo 输出结果echo "$time_period 好, $name!"
}# 调用函数并捕获其输出
user="王女士"
# 使用 $(...) 来获取函数的输出
greeting_message=$(generate_greeting "$user")# 现在 $greeting_message 变量里就存着函数的输出了
echo "从函数获取到的问候语是: $greeting_message"

总结 返回值 vs 输出:

  • 想表示成功/失败状态?用 return N,然后检查 $?
  • 想传递数据/结果 (字符串、数字等)?在函数里用 echo 输出,在外面用 result=$(函数名 参数) 捕获。
  • 别混淆: return 100 主要是设 $?=100 表示一种状态,而 echo 100 是把字符串 “100” 输出到屏幕(可以被捕获)。

二、脚本结构与模块化:搭积木,让代码更整洁

把代码写进函数是提高脚本质量的第一步。更进一步,我们可以思考如何组织这些函数,让整个脚本结构更清晰,甚至让一些通用的函数能在多个脚本之间共享

1.将常用功能封装为函数

想象一下,你在写一个比较大的脚本,里面可能反复需要打印分隔线、记录日志、检查用户输入等。把这些重复出现逻辑独立的功能各自封装成一个函数,好处多多:

  • 代码更简洁: 主流程代码会变短,只剩下调用函数的名字,逻辑一目了然。
  • 复用更容易: 定义一次,到处调用。
  • 维护更方便: 如果分隔线样式要改,只需要改 print_separator 函数就行了。

示例:使用函数打印分隔线

#!/bin/bash# 定义一个打印分隔线的函数
print_separator() {echo "----------------------------------------"
}# --- 主程序逻辑开始 ---
echo "任务一:处理用户数据"
# ... 执行任务一的代码 ...
echo "用户数据处理完毕。"# 调用函数打印分隔线
print_separatorecho "任务二:生成报告"
# ... 执行任务二的代码 ...
echo "报告生成完成。"# 再次调用函数打印分隔线
print_separatorecho "所有任务结束。"

看,通过调用 print_separator,我们避免了重复写 echo "------...",代码是不是清爽多了?

2.脚本的模块化与可重用性 🧩➡️📦

当你的函数库越来越丰富,有些函数(比如日志记录、通用的数学计算、字符串处理等)可能在很多不同的脚本里都会用到。这时,我们可以把这些通用的函数单独存放在一个或多个文件里(比如叫做 my_utils.sh),然后在需要它们的脚本中使用 source 命令(或者一个点 .)把这些函数加载进来。

  • source 命令 (或 .): 它的作用是在当前 Shell 环境中执行指定文件里的命令。如果那个文件里定义了函数,那么执行 source 之后,这些函数在当前脚本里就可用了

概念示例 (不实际运行,仅作演示):

假设你有一个 utils.sh 文件,内容是:

# 文件名: utils.sh
# 一些通用的工具函数log_message() {local level=$1local message=$2echo "[$(date +'%Y-%m-%d %H:%M:%S')] [$level] $message" >> /var/log/my_app.log
}add_numbers() {echo $(($1 + $2))
}

然后,在你的主脚本 main.sh 里,你可以这样做:

#!/bin/bash
# 文件名: main.sh# 使用 source 命令加载 utils.sh 里的函数定义
source ./utils.sh
# 或者用点 . (注意点和文件名之间有空格)
# . ./utils.sh# 现在可以直接使用 utils.sh 里定义的函数了
log_message "INFO" "主脚本开始执行..."num1=10
num2=20
sum=$(add_numbers $num1 $num2) # 调用来自 utils.sh 的函数
echo "$num1 + $num2 = $sum"log_message "INFO" "主脚本执行完毕。"

通过这种方式,log_messageadd_numbers 这些通用功能就实现了模块化,可以被多个脚本重用,大大提高了代码的组织性可维护性。这就是脚本结构优化的重要一步!


三、练习题 🧠✍️

题目一:函数定义与调用
❓ 定义一个名为 show_date 的函数,它不需要参数,执行时打印当前的日期和时间 (可以使用 date 命令)。然后在脚本中调用这个函数。

题目二:函数参数
❓ 定义一个名为 multiply 的函数,接收两个数字作为参数 ($1$2)。函数内部计算这两个数字的乘积,并使用 echo 打印结果,例如 “Result: N”。然后调用这个函数传递参数 5 和 6。

题目三:函数返回值 (退出状态)
❓ 定义一个函数 is_positive,接收一个数字作为参数。如果数字大于 0,函数使用 return 0;否则,使用 return 1。调用该函数传递 -3,并根据 $? 的值打印 “数字是正数” 或 “数字不是正数”。

题目四:函数返回值 (捕获输出)
❓ 定义一个函数 get_system_info,该函数使用 echo 输出字符串 “系统类型: $(uname -s)”。在脚本中调用该函数,并将函数的输出捕获到一个名为 sys_info 的变量中,最后打印这个变量。

题目五:函数的好处
❓ 简单说出使用函数来组织 Shell 脚本代码的主要好处有哪些?(至少说两点)

题目六:模块化概念
❓ 如果你想把一些常用的函数(比如日志函数、检查函数)放到一个单独的文件 common_funcs.sh 里,然后在你的主脚本 my_script.sh 中使用这些函数,你应该在 my_script.sh 的开头使用什么命令来加载 common_funcs.sh


四、参考答案 ✅💡

答案一:

#!/bin/bash
# 定义函数
show_date() {echo "当前日期和时间是: $(date)"
}# 调用函数
echo "准备显示日期..."
show_date
echo "日期显示完毕。"

答案二:

#!/bin/bash
# 定义函数
multiply() {local num1=$1local num2=$2local result=$((num1 * num2))echo "计算结果: $result" # 使用 echo 输出结果
}# 调用函数并传递参数
echo "计算 5 * 6 ..."
multiply 5 6 # 输出 "计算结果: 30"

答案三:

#!/bin/bash
# 定义函数
is_positive() {local number=$1if [ $number -gt 0 ]; thenreturn 0 # 正数,返回 0 (成功)elsereturn 1 # 非正数,返回 1 (失败)fi
}# 调用函数并检查退出状态 $?
check_num=-3
echo "检查数字 $check_num 是否为正数..."
is_positive $check_num
status=$? # 立刻获取返回值if [ $status -eq 0 ]; thenecho "结果: 数字是正数。"
elseecho "结果: 数字不是正数。" # 因为传入 -3,应该输出这个
fi

答案四:

#!/bin/bash
# 定义函数
get_system_info() {# 使用 echo 输出信息echo "系统类型: $(uname -s)"
}# 调用函数并捕获输出到变量 sys_info
echo "正在获取系统信息..."
sys_info=$(get_system_info) # 使用命令替换 $(...)# 打印捕获到的信息
echo "获取到的信息是: $sys_info"

答案五:
使用函数的主要好处包括:

  1. 代码复用 (Reusability): 避免重复编写相同的代码块。
  2. 提高可读性 (Readability): 主逻辑更清晰,代码结构化。
  3. 简化维护 (Maintainability): 修改功能只需改动函数内部。
  4. 模块化 (Modularity): 可以将相关功能组织在一起,甚至放到单独文件。

答案六:
应该在 my_script.sh 的开头使用 source common_funcs.sh 命令,或者用它的简写形式 . common_funcs.sh (注意点.和文件名之间有空格)。

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

相关文章:

  • JavaScript 中的 Proxy 与 Reflect 教程
  • 比特、字节与布尔逻辑:计算机数据存储与逻辑运算的底层基石
  • PMP-第四章 项目整合管理(一)
  • 享元模式(Flyweight Pattern)
  • MOS管极间电容参数学习
  • spring中的@ComponentScan注解详解
  • stm32week14
  • 主机电路安全防护系统哪个厂家做
  • 招聘绩效效果评估方案与优化路径
  • 35、C# 中的反射(Reflection)
  • 深入理解 Spring MVC:DispatcherServlet 与视图解析机制​
  • 快速弄懂POM设计模式
  • 1991年-2023年 上市公司-重污染企业数据 -社科数据
  • GitHub 趋势日报 (2025年05月03日)
  • 多模态大语言模型arxiv论文略读(五十九)
  • STM32教程:ADC原理及程序(基于STM32F103C8T6最小系统板标准库开发)*详细教程*
  • 数电填空题整理(适用期末考试)
  • Linux网络编程:套接字
  • C++类_匿名类
  • 从入门到登峰-嵌入式Tracker定位算法全景之旅 Part 2 |蜂窝 LBS on Tracker:从 AT 命令到定位结果
  • 今天python练习题
  • MYSQL-联合查询
  • 【前端】【总复习】HTML
  • 基于 ESP32 和 GC9D01 0.71寸TFT屏幕的逼真眼睛与写轮眼动态显示
  • Spring Boot Jpa封装快速构建Specification、OrderBy、Pageable的查询条件
  • 【Python】一直没搞懂生成器是什么。。
  • 【25软考网工】第五章(5)ICMP和ICMPv6、NDP、IP组播技术和MPLS
  • JavaScript基础-分支流程控制
  • strstr()和strpbrk()函数的区别
  • 学习黑客开源情报