🐚 Shell 编程进阶:函数、变量作用域与数组全面解析
在学习 Shell 脚本的过程中,很多人一开始都觉得写个小脚本挺简单:几行命令拼在一起,能跑就行。但当脚本逐渐变复杂,就会遇到很多看似“小问题”,比如:
- 为什么函数里定义的变量会影响到外面?
- 局部变量和全局变量的区别究竟是什么?
- Shell 的数组和其他语言里的数组完全一样吗?
这些问题看似细节,实则关乎脚本的健壮性和可维护性。如果不搞清楚,就容易写出“能跑,但一改就崩”的脚本。本文将带你系统理解 函数、变量作用域与访问优先级、数组 三个重要主题,并通过案例和类比让你能快速消化。
一、函数:脚本的“工具箱”
为什么要用函数?
在写脚本时,如果逻辑越来越长,复制粘贴同一段命令往往不可避免。但复制意味着风险:一旦逻辑需要修改,你得改所有地方,稍有遗漏就出 Bug。
函数的存在就是为了解决这个问题。它把一段逻辑“封装”起来,像工具箱里的扳手一样,需要时拿来用,不用时就收起来。这不仅减少重复,也让脚本更易读。
基本用法
function greet() {
echo "Hello, $1"
}
say_goodbye() {
echo "Goodbye, $1"
}
- 函数有两种定义方式,带不带
function都可以。 - 参数通过
$1, $2这样的形式传入,$0是脚本自身的名字。
实战案例:日志函数
实际开发中,一个非常常见的需求是打印日志。与其到处写 echo,不如写个日志函数:
log() {
local level=$1
shift
echo "[$(date '+%F %T')] [$level] $*"
}
log INFO "开始执行脚本"
log ERROR "文件不存在"
输出结果非常直观:
[2025-09-01 15:20:01] [INFO] 开始执行脚本
[2025-09-01 15:20:02] [ERROR] 文件不存在
👉 可以把函数理解成“胶水”,把零散的命令粘合成模块化的功能。
二、变量作用域与访问优先级
全局与局部的区别
Shell 的变量和很多编程语言不一样:默认情况下,所有变量都是全局的。这意味着你在函数里改动一个变量,很可能直接影响到外面。
x=10
foo() {
x=20
}
foo
echo $x # 输出 20
这种“污染”可能让调试变得异常痛苦。于是 local 关键字应运而生。它让变量的作用范围仅限于函数内部:
x=10
foo() {
local x=20
echo "函数内: $x"
}
foo
echo "函数外: $x" # 仍然是 10
👉 类比一下:
- 全局变量 = 公司大茶水间里的零食,谁都能拿;
- 局部变量 = 你办公桌抽屉里的小零食,只你自己能吃。
访问优先级
当函数内外都有同名变量时,Shell 会优先使用局部变量:
name="global"
foo() {
local name="local"
echo "函数内: $name"
}
foo
echo "函数外: $name"
输出:
函数内: local
函数外: global
此外,如果变量被 readonly 修饰,就相当于“上了锁”,无论在什么作用域都不能被修改。
三、数组:批量处理数据的利器
在运维脚本中,很多时候需要同时处理一批数据,比如服务列表、文件路径、用户账号等。这时数组就派上用场。
一维数组
arr=(apple banana cherry)
echo ${arr[0]} # apple
echo ${arr[2]} # cherry
遍历数组:
for item in "${arr[@]}"; do
echo $item
done
⚡ 小提示:
${arr[@]}表示所有元素,逐个展开;${arr[*]}在某些上下文会被当成一个整体字符串。
操作数组
arr=(10 20 30)
arr[1]=200 # 修改
echo ${#arr[@]} # 获取长度
echo ${!arr[@]} # 获取索引列表
unset arr[1] # 删除某个元素
echo ${arr[@]} # 10 30
关联数组
Bash 4.0+ 提供了“键值对”形式的数组,类似 Python 字典:
declare -A scores
scores["Alice"]=90
scores["Bob"]=85
echo ${scores["Alice"]} # 90
👉 类比理解:
- 普通数组 = 一排格子(用编号取值);
- 关联数组 = 字典(用名字取值)。
四、综合实战:服务监控小脚本
把函数、变量作用域和数组结合,就能写出简洁而强大的工具。比如一个服务监控脚本:
#!/bin/bash
log() {
local level=$1
shift
echo "[$(date '+%F %T')] [$level] $*"
}
check_services() {
local services=("$@")
for s in "${services[@]}"; do
if systemctl is-active --quiet "$s"; then
log INFO "服务 $s 正常运行"
else
log ERROR "服务 $s 异常停止"
fi
done
}
services=("nginx" "mysql" "redis")
check_services "${services[@]}"
执行效果:
[2025-09-01 15:30:01] [INFO] 服务 nginx 正常运行
[2025-09-01 15:30:01] [ERROR] 服务 mysql 异常停止
[2025-09-01 15:30:01] [INFO] 服务 redis 正常运行
这就是一个实际可用的“迷你监控工具”,几乎能直接上生产。
五、结语
回顾一下本文的三个重点:
- 函数:让脚本模块化、复用化,减少重复劳动。
- 变量作用域:通过
local控制作用范围,避免全局污染。 - 数组:批量管理数据,既支持顺序存储,又支持键值对。
学会这三块内容,你会发现 Shell 不再只是“拼命写命令”的工具,而是可以构建优雅、健壮、可维护脚本的语言。
如果你经常写脚本,强烈建议多用函数封装逻辑、合理规划变量作用域,并用数组管理数据结构——这样你的脚本将不仅能跑,还能跑得漂亮。
Shell:命令解释器,是用户与操作系统内核交互的桥梁。常见的有 bash、zsh、sh。
Bash (Bourne Again Shell):最常见的 Shell,是 Linux 系统默认使用的。
脚本 (Script):由一系列命令组成的文件,可以被 Shell 顺序执行。
Shebang (#!):脚本开头的声明,指定用哪个解释器执行,例如 #!/bin/bash。
内建命令 (Builtin command):Shell 自身提供的命令,如 cd、echo,无需额外进程。
外部命令 (External command):独立的可执行程序,如 ls、grep,执行时会启动新进程。
环境变量 (Environment variable):操作系统级别的变量,影响 Shell 和子进程运行环境,比如 PATH、HOME。
位置参数 (Positional parameters):函数或脚本运行时传入的参数,用 $1, $2, … 访问。
退出状态码 (Exit status):命令执行的结果,0 表示成功,非 0 表示失败,可通过 $? 获取。
340

被折叠的 条评论
为什么被折叠?



