Bash 进阶实战:函数、数组与正则表达式全解析
在 Linux 系统管理和自动化运维中,Bash 脚本是不可或缺的工具。掌握函数、数组和正则表达式这三大核心特性,能让你的脚本从“简单指令堆砌”升级为“模块化、高效化的工具”。本文将结合实例,详细讲解这三大特性的用法,帮你夯实 Bash 进阶基础。
一、Bash 自定义函数:让代码更模块化
函数的核心价值是“代码复用”——把重复执行的逻辑封装成独立模块,既减少冗余,又便于维护。Bash 中函数的定义和调用都非常简洁,且支持参数传递与返回值处理。
1.1 函数的定义格式
Bash 函数有两种常用定义方式,推荐使用第一种(结构更清晰):
# 方式1:标准格式(推荐)
function 函数名() {
命令序列 # 函数体:可包含普通命令、流程控制(if/for等)
}
# 方式2:简化格式(省略 function 关键字)
函数名() {
命令序列
}
注意事项:
- 函数名需符合“字母/数字/下划线”规则,且不能以数字开头(如
sum1
合法,1sum
非法); - 函数体的
{}
前后需留空格(或换行),否则会语法报错; - 函数必须先定义,再调用(Bash 按顺序执行脚本,未定义的函数无法调用)。
1.2 函数的调用:直接用函数名
调用函数无需加括号,直接写“函数名”即可。如果需要传递参数,在函数名后紧跟参数(空格分隔)。
示例1:无参数函数
#!/bin/bash
# 定义一个打印欢迎信息的函数
function print_welcome() {
echo "====================="
echo " 欢迎使用 Bash 工具 "
echo "====================="
}
# 调用函数(直接写函数名)
print_welcome
执行结果:
=====================
欢迎使用 Bash 工具
=====================
1.3 函数的参数传递:用 $n 接收
Bash 函数不支持“形参定义”,而是通过 $n
接收外部传递的参数($1
表示第1个参数,$2
表示第2个,以此类推)。若参数序号≥10,需用 ${10}
表示(避免与 $1
+0
混淆)。
示例2:带参数的求和函数
#!/bin/bash
# 定义求和函数:接收2个参数,计算并打印结果
function sum() {
# $1 接收第一个参数,$2 接收第二个参数
local num1=$1 # local 关键字:定义局部变量(仅函数内可用)
local num2=$2
# 验证输入是否为数字(避免计算报错)
if ! [[ "$num1" =~ ^[0-9]+$ ]] || ! [[ "$num2" =~ ^[0-9]+$ ]]; then
echo "错误:请输入有效的正整数!"
return 1 # 返回错误状态码(非0表示失败)
fi
local result=$((num1 + num2))
echo "求和结果:$num1 + $num2 = $result"
}
# 调用函数:传递2个参数(10和20)
sum 10 20
# 再次调用:传递其他参数
sum 30 45
执行结果:
求和结果:10 + 20 = 30
求和结果:30 + 45 = 75
1.4 函数的返回值:用 $? 接收
Bash 函数的返回值有两种常见形式:
- 状态码返回:用
return 数值
返回(仅支持 0-255 的整数,0 表示成功,非0表示失败),外部通过$?
获取; - 结果返回:若需返回字符串或大于255的数字,可在函数内用
echo
输出结果,外部通过变量=$(函数名)
捕获。
示例3:两种返回值的用法
#!/bin/bash
# 1. 状态码返回:判断数字是否为偶数
function is_even() {
local num=$1
if (( num % 2 == 0 )); then
return 0 # 是偶数:返回成功状态码
else
return 1 # 不是偶数:返回失败状态码
fi
}
# 2. 结果返回:计算数字的平方(返回大于255的结果)
function square() {
local num=$1
echo $((num * num)) # 用 echo 输出结果
}
# 测试状态码返回
is_even 12
if [ $? -eq 0 ]; then # $? 获取上一个命令(函数)的返回码
echo "12 是偶数"
else
echo "12 是奇数"
fi
# 测试结果返回
result=$(square 20) # 捕获函数的 echo 输出
echo "20 的平方是:$result"
执行结果:
12 是偶数
20 的平方是:400
二、Bash 数组:高效管理批量数据
当需要处理“一组相关数据”(如服务器列表、日志文件名、配置参数)时,数组是最优选择。Bash 支持一维数组(不支持多维),可灵活实现数据的定义、读取、遍历和修改。
2.1 数组的定义:3种常见方式
Bash 数组无需声明长度,直接赋值即可,元素间用空格分隔。
# 方式1:直接定义(最常用)
array_name=(元素1 元素2 元素3 ...)
server_list=("192.168.1.10" "192.168.1.20" "192.168.1.30") # 示例:服务器IP数组
# 方式2:单独定义元素(指定索引)
array_name[索引]=元素
fruit[0]="apple"
fruit[1]="banana"
fruit[5]="orange" # 索引可跳过,未赋值的索引默认为空
# 方式3:从命令输出创建数组(将命令结果按空格分割为数组元素)
file_list=($(ls /home/user/docs)) # 示例:将 /home/user/docs 下的文件存入数组
2.2 数组的读取:获取单个或所有元素
数组元素的索引从 0 开始,通过 ${数组名[索引]}
读取单个元素;通过 ${数组名[@]}
或 ${数组名[*]}
读取所有元素。
示例4:读取数组元素
#!/bin/bash
# 定义数组
fruit=("apple" "banana" "orange" "grape")
# 1. 读取指定索引的元素
echo "索引0的元素:${fruit[0]}" # 输出 apple
echo "索引2的元素:${fruit[2]}" # 输出 orange
# 2. 读取所有元素(两种方式)
echo "所有元素(@):${fruit[@]}" # 输出 apple banana orange grape
echo "所有元素(*):${fruit[*]}" # 输出 apple banana orange grape
# 3. 获取数组长度(元素个数)
echo "数组长度:${#fruit[@]}" # 输出 4
2.3 数组的遍历:两种实用方式
遍历数组即“逐个处理元素”,常用 for
循环实现,根据场景选择不同方式。
方式1:直接遍历所有元素(推荐,无需关心索引)
#!/bin/bash
fruit=("apple" "banana" "orange" "grape")
echo "方式1:遍历所有元素"
for item in "${fruit[@]}"; do # 用 @ 确保元素包含空格时也能正确处理
echo "水果:$item"
done
方式2:按索引遍历(需处理索引时用,如修改元素)
#!/bin/bash
fruit=("apple" "banana" "orange" "grape")
length=${#fruit[@]} # 获取数组长度
echo -e "\n方式2:按索引遍历"
for ((i=0; i<length; i++)); do
echo "索引$i:${fruit[$i]}"
# 示例:修改元素(给每个水果加前缀)
fruit[$i]="fresh_${fruit[$i]}"
done
# 输出修改后的数组
echo -e "\n修改后的数组:${fruit[@]}"
执行结果:
方式1:遍历所有元素
水果:apple
水果:banana
水果:orange
水果:grape
方式2:按索引遍历
索引0:apple
索引1:banana
索引2:orange
索引3:grape
修改后的数组:fresh_apple fresh_banana fresh_orange fresh_grape
三、Bash 正则表达式:精准过滤文本数据
正则表达式(简称“正则”)是“描述字符串模式的规则”,核心用途是检索、过滤、替换符合规则的文本。在 Bash 中,常用 grep
命令结合正则处理日志、配置文件等文本数据。
3.1 常用工具:grep 与正则搭配
grep
是 Bash 中最常用的文本过滤工具,支持基础正则和扩展正则(需加 -E
参数),以下是高频选项:
选项 | 功能说明 |
---|---|
-E |
启用扩展正则(无需对 {}、+、? 等元字符转义) |
-c |
统计匹配到的行数 |
-i |
忽略大小写(如匹配 Apple 和 apple ) |
-o |
只输出匹配到的内容(而非整行) |
-v |
反向匹配(输出不包含匹配内容的行) |
-n |
显示匹配行的行号 |
--color=auto |
高亮显示匹配到的内容(终端中更易识别) |
3.2 核心元字符:构建正则规则
正则的核心是“元字符”——具有特殊含义的字符,掌握这些元字符就能组合出任意规则。以下是 Bash 中常用的元字符(分基础版和进阶版):
基础元字符(必掌握)
元字符 | 含义 | 示例 |
---|---|---|
^ |
匹配行首(开头位置) | ^root :匹配以 root 开头的行 |
$ |
匹配行尾(结束位置) | bash$ :匹配以 bash 结尾的行 |
. |
匹配除换行符(\n)外的任意单个字符 | r..t :匹配 root 、rest 等 |
[list] |
匹配“list”中的任意一个字符 | [abc] :匹配 a 、b 或 c |
[^list] |
反向匹配(匹配“list”之外的任意一个字符) | [^0-9] :匹配非数字字符 |
* |
匹配前面的子表达式“0次或多次” | ro* t :匹配 rt 、rot 、root 等 |
进阶元字符(扩展正则,需加 -E
)
元字符 | 含义 | 示例 |
---|---|---|
{n} |
精确匹配“前面的子表达式”n次 | o{2} :匹配 oo (如 food 中的 oo ) |
{n,} |
至少匹配“前面的子表达式”n次 | o{2,} :匹配 oo 、ooo 等 |
{n,m} |
匹配“前面的子表达式”n到m次(n≤m) | o{1,2} :匹配 o 或 oo |
+ |
匹配“前面的子表达式”1次或多次(等价于 {1,} ) |
o+ :匹配 o 、oo 等 |
? |
匹配“前面的子表达式”0次或1次(等价于 {0,1} ) |
o? :匹配空或 o |
3.3 实战示例:用正则解决实际问题
以下示例基于 /etc/passwd
文件(Linux 系统用户配置文件),演示正则的实际用法。
示例5:匹配以 root 开头的行(行首匹配 ^
)
# 匹配以 root 开头的行,并显示行号
grep -n "^root" /etc/passwd
结果(类似):
1:root:x:0:0:root:/root:/bin/bash
示例6:匹配以 bash 结尾的行(行尾匹配 $
)
# 匹配以 bash 结尾的行,高亮显示
grep --color=auto "bash$" /etc/passwd
结果(类似):
root:x:0:0:root:/root:/bin/bash
user:x:1000:1000:user:/home/user:/bin/bash
示例7:匹配包含 3 个数字的行(用 [0-9]
和 {3}
)
# 启用扩展正则(-E),匹配包含 3 个连续数字的行,统计行数(-c)
grep -Ec "[0-9]{3}" /etc/passwd
结果(类似):
5 # 表示有5行包含3个连续数字
示例8:反向匹配(排除以 # 开头的注释行)
# 查看 /etc/ssh/sshd_config,排除注释行(-v)和空行(^$)
grep -vE "^#|^$" /etc/ssh/sshd_config
结果:仅显示配置文件中的有效配置行(无注释、无空行)。
四、总结:三大特性的核心应用场景
特性 | 核心价值 | 典型应用场景 |
---|---|---|
自定义函数 | 代码复用、模块化 | 封装重复逻辑(如日志打印、参数验证)、构建脚本框架 |
数组 | 批量管理相关数据 | 存储服务器列表、文件列表、配置参数,实现批量操作(如批量 ping 服务器) |
正则表达式 | 精准过滤、检索文本 | 分析日志(如提取错误信息)、处理配置文件(如筛选有效配置)、验证输入格式 |
掌握这三大特性后,你可以写出更简洁、高效、易维护的 Bash 脚本。建议结合实际需求多练手(如写一个“批量检查服务器存活状态”的脚本,用数组存服务器IP,用函数封装 ping 逻辑,用正则过滤 ping 结果),逐步提升 Bash 实战能力。