变量能代替一个不固定的字符串,但如果有 100 个不固定的字符串呢?难道要定义 100 个变量吗?
数组就是用来解决这个问题的——用一个固定的名字,代替多个不固定的值。函数则是将重复使用的代码打包成模块,让脚本更清晰、更可复用。
本文将系统讲解 Shell 数组(普通数组、关联数组)、函数(定义、传参、返回值)、内置控制命令(break、continue、shift),并通过实战案例带你掌握数组统计和函数模块化编程的完整知识体系。
数组
数组简介
| 概念 | 说明 |
|---|---|
| 变量 | 用一个固定的字符串,代替一个不固定的字符串 |
| 数组 | 用一个固定的字符串,代替多个不固定的字符串 |
数组类型
| 类型 | 说明 | 索引类型 |
|---|---|---|
| 普通数组 | 只能使用整数作为数组索引 | 整数(0, 1, 2…) |
| 关联数组 | 可以使用字符串作为数组索引 | 字符串(如 “name”、”age”) |
类比理解:
变量: name = "tom" ← 一个名字对应一个值
数组: names[0] = "tom" ← 一个数组名对应多个值
names[1] = "jack"
names[2] = "alice"
关联数组: 可以自定义索引值

普通数组
方法一:一次赋一个值
# 定义数组
array1[0]=pear
array1[1]=apple
array1[2]=orange
array1[3]=peach
# 查看数组
[root@localhost ~]# declare -a | grep array1
declare -a array1='([0]="pear" [1]="apache" [2]="orange" [3]="peach")'
# 访问数组元素
[root@localhost ~]# echo ${array1[@]}
pear apache orange peach
方法二:一次赋多个值
# 直接赋值
array2=(tom jack alice)
# 命令结果赋值(文件每行作为一个元素)
array3=(`cat /etc/passwd`)
# 通配符赋值
array4=(`ls /var/ftp/Shell/for*`)
# 混合赋值(包含空格和自定义索引)
array5=(tom jack alice "bash shell")
# 使用变量
colors=($red $blue $green $recolor)
# 混合索引赋值
array6=(1 2 3 4 5 6 7 "linux shell" [20]=saltstack)
访问数组元素
| 语法 | 说明 | 示例 |
|---|---|---|
${array[0]} | 访问第一个元素 | echo ${array1[0]} |
${array[@]} 或 ${array[*]} | 访问所有元素 | echo ${array1[@]} |
${#array[@]} | 统计数组元素个数 | echo ${#array1[@]} |
${!array[@]} | 获取数组元素的索引 | echo ${!array2[@]} |
${array[@]:1} | 从数组下标 1 开始访问 | echo ${array1[@]:1} |
${array[@]:1:2} | 从下标 1 开始,访问 2 个元素 | echo ${array1[@]:1:2} |
示例演示:
[root@localhost ~]# array1=(pear apple orange peach)
# 访问第一个元素
[root@localhost ~]# echo ${array1[0]}
pear
# 访问所有元素
[root@localhost ~]# echo ${array1[@]}
pear apple orange peach
# 统计元素个数
[root@localhost ~]# echo ${#array1[@]}
4
# 获取索引
[root@localhost ~]# echo ${!array1[@]}
0 1 2 3
# 从下标 1 开始访问
[root@localhost ~]# echo ${array1[@]:1}
apple orange peach
# 从下标 1 开始,访问 2 个元素
[root@localhost ~]# echo ${array1[@]:1:2}
apple orange
关联数组
重要:使用关联数组前,必须先使用
declare -A声明。
方法一:一次赋一个值
# 声明关联数组
declare -A ass_array1
# 逐个赋值
ass_array1[index1]=pear
ass_array1[index2]=apple
ass_array1[index3]=orange
ass_array1[index4]=peach
# 查看
[root@localhost ~]# echo ${ass_array1[@]}
pear apple orange peach
修改某个值
[root@localhost ~]# declare -A lol
[root@localhost ~]# lol[down1]=aaa
[root@localhost ~]# lol[down2]=bbb
[root@localhost ~]# lol[down3]=ccc
[root@localhost ~]# echo ${lol[@]}
aaa bbb ccc
# 修改 down2 的值
[root@localhost ~]# lol[down2]=ddd
[root@localhost ~]# echo ${lol[@]}
aaa ddd ccc
方法二:一次赋多个值
declare -A ass_array2
ass_array2=([index1]=tom [index2]=jack [index3]=alice [index4]='bash shell')
查看关联数组
[root@localhost ~]# declare -A
declare -A ass_array1='([index4]="peach" [index1]="pear" [index2]="apple" [index3]="orange" )'
declare -A ass_array2='([index4]="bash shell" [index1]="tom" [index2]="jack" [index3]="alice" )'
访问关联数组元素
| 语法 | 说明 | 示例 |
|---|---|---|
${array[index2]} | 访问指定索引的元素 | echo ${ass_array2[index2]} |
${array[@]} | 访问所有元素 | echo ${ass_array2[@]} |
${#array[@]} | 统计元素个数 | echo ${#ass_array2[@]} |
${!array[@]} | 获取所有索引 | echo ${!ass_array2[@]} |
数组和循环
案例 1:while 脚本快速定义数组
#!/bin/bash
# 循环读取文件,定义数组
while read line
do
# hosts: 数组名
# [++i]: 索引递增,++i 从 1 开始,i++ 从 0 开始
# $line: 值,即文件中的内容
hosts[++i]=$line
done < /etc/hosts
# 输出索引每一行
for i in ${!hosts[@]}
do
echo "$i : ${hosts[$i]}"
done
测试:
[root@localhost ~]# bash arr.sh
1 : 127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
2 : ::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
[root@localhost ~]#
++i vs i++ 区别:
# i++ 从 0 开始
[root@localhost ~]# aaa[i++]=111
[root@localhost ~]# aaa[i++]=222
[root@localhost ~]# aaa[i++]=333
[root@localhost ~]# echo ${!aaa[@]}
0 1 2
[root@localhost ~]# echo ${aaa[@]}
111 222 333
# ++i 从 1 开始
[root@localhost ~]# ccc[++k]=111
[root@localhost ~]# ccc[++k]=222
[root@localhost ~]# ccc[++k]=333
[root@localhost ~]# echo ${!ccc[@]}
1 2 3
[root@localhost ~]# echo ${ccc[@]}
111 222 333
案例 2:for 脚本快速定义数组
#!/bin/bash
for line in `cat /etc/hosts`
do
hosts[++i]=$line
done
for i in ${!hosts[@]}
do
echo "$i: ${hosts[$i]}"
done
测试:
[root@localhost ~]# bash for_arr.sh
1: 127.0.0.1
2: localhost
3: localhost.localdomain
4: localhost4
5: localhost4.localdomain4
6: ::1
7: localhost
8: localhost.localdomain
9: localhost6
10: localhost6.localdomain6
[root@localhost ~]#
问题:for 的空格分割
for 循环默认以空格分割,导致每行被拆分成多个元素。
解决方法:在脚本中使用 IFS 重新定义分隔符
# 保存原始 IFS
OLD_IFS="$IFS"
# 设置为换行符
IFS=$'\n'
# 脚本内容~~~~~
# 处理完后恢复
IFS="$OLD_IFS"
案例 3:数组统计性别
性别文件:
[root@localhost ~]# vim sex.txt
jack m
alice f
tom m
统计脚本:
#!/bin/bash
declare -A sex
while read line
do
type=`echo $line | awk '{print $2}'`
let sex[$type]++
done < sex.txt
for i in ${!sex[@]}
do
echo "$i : ${sex[$i]}"
done
测试:
[root@localhost ~]# bash sex.sh
f : 1
m : 2
let sex[$type]++ 原理说明:
[root@localhost ~]# declare -A sex
[root@localhost ~]# let sex[m]++
[root@localhost ~]# let sex[m]++
[root@localhost ~]# echo ${sex[@]}
2
[root@localhost ~]# echo ${!sex[@]}
m
[root@localhost ~]# let sex[f]++
[root@localhost ~]# let sex[f]++
[root@localhost ~]# let sex[f]++
[root@localhost ~]# echo ${sex[@]}
3 2
[root@localhost ~]# echo ${!sex[@]}
f m
let会自动将未初始化的变量视为 0,然后自增。
案例 4:统计用户 Shell 类型和数量
#!/bin/bash
declare -A shells
while read ll
do
type=`echo $ll | awk -F: '{print $NF}'`
let shells[$type]++
done < /etc/passwd
for i in ${!shells[*]}
do
echo "$i: ${shells[$i]}"
done
原理说明:
awk -F: '{print $NF}':以:为分隔符,提取最后一列(用户的 Shell 类型)$NF:表示最后一个字段
函数
概述
函数是一段完成特定功能的代码片段(块)。在 Shell 中定义了函数,就可以使代码模块化,便于复用代码。
重点:
| 重点 | 说明 |
|---|---|
| 传参 | $1, $2 等位置参数 |
| 局部变量 | local 关键字 |
| 返回值 | return,即 $? |
注意:函数必须先定义才可以使用。
定义函数
# 方法一
函数名() {
函数要实现的功能代码
}
# 方法二
function 函数名 {
函数要实现的功能代码
}
调用函数
# 无参数调用
函数名
# 带参数调用
函数名 参数1 参数2
示例 1:系统工具箱(初识函数)
需求:编写系统工具箱菜单,循环提供功能选择。
菜单:
工具菜单:
显示磁盘信息(d)
显示内存信息(m)
显示CPU信息(c)
退出(q)
#!/bin/bash
show_menu() {
cat << EOF
工具菜单:
显示磁盘信息(d)
显示内存信息(m)
显示CPU信息(c)
退出(q)
EOF
}
while :
do
show_menu
read -p "输入选项: " choice
case $choice in
d)
echo "===========磁盘信息=============="
df -hT
;;
m)
echo "==========内存信息==============="
free -m
;;
c)
echo "==========CPU信息================="
uptime
;;
q)
break
;;
*)
show_menu
;;
esac
done
示例 2:阶乘函数(传参)
基础版本:
#!/bin/bash
fun1() {
factorial=1
for((i=1; i<=5; i++))
do
factorial=$[$factorial * $i]
done
echo "5的阶乘是: $factorial"
}
fun1
for((i=1; i<=5; i++))等同于for i in $(seq 5)。i<=5是阶乘的上限。
优化 1:传参,让函数能够自定义
#!/bin/bash
fun1() {
factorial=1
for((i=1; i<=$1; i++))
do
factorial=$[$factorial * $i]
done
echo "$1 的阶乘是: $factorial"
}
fun1 3
$1是函数的第一个参数,即阶乘的上限。
优化 2:由脚本外部传递参数
#!/bin/bash
fun1() {
factorial=1
for((i=1; i<=$1; i++))
do
factorial=$[$factorial * $i]
done
echo "$1 的阶乘是: $factorial"
}
fun1 $1
fun1 $2
fun1 $3
测试:
[root@localhost ~]# bash cc.sh 3 5 10
3 的阶乘是: 6
5 的阶乘是: 120
10 的阶乘是: 3628800
重要区别:
bash cc.sh 3 5 10 ← 这是脚本的参数
fun1 $1 ← 这是函数在调用脚本的参数
函数传参能够在模块化的命令集中,添加自定义部分。参数应加在调用函数的后面。Shell 脚本的参数会先传给函数,函数再转换到内部使用。
优化 3:多种运算表达式
#!/bin/bash
fun1() {
factorial=1
# 三种循环写法等价:
# for((i=1; i<=$1; i++))
for i in `seq $1`
do
# 三种运算写法等价:
# factorial=$[$factorial * $i]
# let factorial=$factorial*$i
let factorial*=$i
done
echo "$1 的阶乘是: $factorial"
}
fun1 $1
示例 3:数组传参给函数
#!/bin/bash
# 先定义一个数组
num=(1 2 3)
# 定义函数
array() {
factorial=1
for i in $* # 重点:遍历所有参数
do
# 阶乘公式
factorial=$[factorial * $i]
done
echo $factorial
}
# 调用函数,使用数组(重点)
array ${num[*]}
测试:
[root@localhost ~]# ./fun3.sh
6
数组的好处:当有多个数组时,传参的效率就提高了。函数通过
$*或$@接收所有参数。
示例 4:函数结果赋予数组
场景:用户获赠流量包(每人增加 5G),结果运算。
#!/bin/bash
num=(1 2 3)
array() {
# local 定义变量仅在函数中有效
local j
# 设置循环的次数,等于索引总数
for i in $*
do
# 定义不同的值乘以 5
outarray[j++]=$[$i + 5]
done
# 输出新的数组
echo "${outarray[*]}"
}
array ${num[*]}
测试:
[root@localhost ~]# bash function_array.sh
6 7 8
总结:函数不仅可以从数组中调取值,还可以赋予数组值。
影响 Shell 程序的内置命令
概览
| 命令 | 说明 |
|---|---|
: | 永远返回真(空命令) |
true | 永远返回真 |
false | 永远返回假 |
exit | 退出整个程序 |
break | 结束当前循环,或跳出本层循环 |
continue | 忽略本次循环剩余的代码,直接进入下一次循环 |
shift | 使位置参数向左移动,默认移动 1 位,可以使用 shift 2 |
嵌套循环实战
需求:通过循环脚本,输出如下效果。
A123456789
B123456789
C123456789
D123456789
步骤 1:基础循环
#!/bin/bash
for i in {A..D}
do
echo $i
for j in {1..9}
do
echo $j
done
done
默认
echo输出换行符,导致每个字符都换行。
步骤 2:解决换行问题
#!/bin/bash
for i in {A..D}
do
echo -n $i
for j in {1..9}
do
echo -n $j
done
done
测试:
A123456789B123456789C123456789D123456789
步骤 3:外循环添加空行
#!/bin/bash
for i in {A..D}
do
echo -n $i
for j in {1..9}
do
echo -n $j
done
# 空行
echo
done
测试:
A123456789
B123456789
C123456789
D123456789
循环嵌套规则:外部循环一次,内部循环全部执行完。
continue 登场
需求:跳过数字 5 的循环。
#!/bin/bash
for i in {A..D}
do
echo -n $i
for j in {1..9}
do
if [ $j -eq 5 ]; then
continue
fi
echo -n $j
done
echo
done
测试:
A12346789
B12346789
C12346789
D12346789
continue:跳过本次循环剩余代码,直接进入下一次循环。
break 登场
需求:遇到 5 时,跳出整个循环。
#!/bin/bash
for i in {A..D}
do
echo -n $i
for j in {1..9}
do
if [ $j -eq 5 ]; then
break 2
fi
echo -n $j
done
echo
done
测试:
A1234
break 2:跳出 2 层循环。break默认跳出当前循环,break n跳出 n 层。
shift 移动参数
案例 1:for 循环求和
#!/bin/bash
for i
do
let sum+=$i
done
echo "sum : $sum"
for i不定义循环范围时,默认遍历所有位置参数($@)。
测试:
[root@localhost ~]# bash sum.sh 1 3
sum : 4
[root@localhost ~]# bash sum.sh 1 2 3
sum : 6
案例 2:while 循环求和(使用 shift)
#!/bin/bash
while [ $# -ne 0 ]
do
let sum+=$1
shift
done
echo "sum : $sum"
测试:
[root@localhost ~]# bash sum.sh 1 2
sum : 3
[root@localhost ~]# bash sum.sh 1 2 3
sum : 6
shift 原理:
初始:$1=1 $2=2 $3=3 $# = 3
shift 后:$1=2 $2=3 $# = 2
shift 后:$1=3 $# = 1
shift 后: $# = 0 → 退出循环
案例 3:创建用户(理解参数位移)
#!/bin/bash
while [ $# -ne 0 ]
do
useradd $1
echo "$1 is created"
shift
done
测试:
[root@localhost ~]# bash shift_user.sh aa bb
aa is created
bb is created
常见问题
数组索引从 0 开始还是从 1 开始?
都可以。i++ 从 0 开始,++i 从 1 开始。根据实际需求选择。
如何删除数组元素?
unset array[0] # 删除索引为 0 的元素
unset array # 删除整个数组
break 和 continue 的区别
| 命令 | 效果 |
|---|---|
continue | 跳过本次循环剩余代码,继续下一次循环 |
break | 结束整个循环 |
break n | 跳出 n 层嵌套循环 |
shift 可以移动多位吗?
可以。shift 2 表示位置参数向左移动 2 位。
局部变量 local 的作用
myfunc() {
local var="local value"
echo $var
}
local声明的变量仅在函数内部有效,不会影响外部同名变量。
最佳实践
使用关联数组做键值映射
declare -A config
config[host]="localhost"
config[port]="3306"使用数组做数据统计
declare -A count
while read line; do
let count[$line]++
done < data.txt函数传参使用
$@myfunc() {
for arg in "$@"; do
echo "处理: $arg"
done
}函数内使用 local 变量
myfunc() {
local result
result=$(some_command)
echo $result
}shift 遍历所有参数
while [ $# -ne 0 ]; do
process $1
shift
done
总结
通过本章节的学习,你应该掌握了:
- 理解数组的概念和两种类型(普通数组、关联数组)
- 掌握普通数组的定义方法(一次赋一个值、一次赋多个值)
- 掌握数组的访问操作(单个元素、所有元素、统计个数、获取索引、切片)
- 掌握关联数组的声明和使用(
declare -A) - 掌握 while/for 循环快速定义数组的方法
- 掌握使用数组进行数据统计(性别统计、Shell 类型统计)
- 理解函数的概念和作用(代码模块化、复用)
- 掌握函数的两种定义方法和调用方式
- 掌握函数传参(
$1、$2、$@、$*) - 掌握局部变量
local的使用 - 掌握数组与函数的结合使用(数组传参、函数结果赋予数组)
- 掌握内置控制命令(break、continue、shift)
- 理解嵌套循环的执行规则
学习建议:数组和函数是 Shell 编程进阶的核心。建议在虚拟机上动手练习每一个案例,特别是数组统计和函数传参。数组结合关联数组做数据统计是非常实用的技能,函数结合数组可以实现更复杂的运维自动化任务。