函数与数组

3480 字
17 分钟
函数与数组

函数 && 数组#

[TOC]


函数#

基本概念#

变量是一个名字对应一个值,==函数是一个名字对应一堆命令==——把完成特定功能的代码块打个包、起个名,需要时喊名字就行

Terminal window
# 函数只定义、不调用则不执行
函数名(){
命令集
}
# 函数必须先定义再调用
# 函数名不能和系统命令重名
Important

📌 ==变量==是一个名字对应一个值 📌 ==函数==是一个名字对应一堆命令

把重复的活儿打包成函数,一次定义,到处调用


1) 函数定义的三种方式#

[root@shell ~]#cat fun.sh
#!/bin/bash
fun1(){
echo "第一种定义方法"
}
# 最常用写法——函数名+()+{}
function fun2(){
echo "第二种定义方法"
}
# 加function关键字,更像其他语言
function fun3 {
echo "第三种定义方式"
}
# function + 函数名 + {命令},省略括号
fun1
fun2
fun3
# 调用函数——喊名字就执行里面的全部命令
'三种写法等效,第一种最简洁,也是最常用的'

2) 函数的传参#

函数传参有三种方式——和脚本传参是同一套逻辑

方法1:在函数后传参(位置参数)#

1)传一个参数——$1
[root@shell ~]#cat fun.sh
#!/bin/bash
fun(){
if [ -f $1 ];then
echo $1 存在
else
echo $1 不存在
fi
}
fun /etc/hosts
[root@shell ~]#sh fun.sh
/etc/hosts 存在
# fun后跟参数,函数内用$1接收
'函数传参和脚本传参一样——$1 $2 $3...'
========================================
2)传多个参数——$1 $2
========================================
[root@shell ~]#cat fun.sh
#!/bin/bash
fun(){
if [ -f $2 ];then
echo $2 存在
else
echo $2 不存在
fi
}
fun /etc/hosts /etc/passwd
[root@shell ~]#sh fun.sh
/etc/passwd 存在
# 传了两个参数,函数内取$2
========================================
3)脚本参数和函数参数的关系
========================================
[root@shell ~]#cat fun.sh
#!/bin/bash
fun(){
if [ -f $2 ];then
echo $2 存在
else
echo $2 不存在
fi
}
fun $2 $1
[root@shell ~]#sh fun.sh /etc/hosts /etc/passwd
/etc/hosts 存在
# 脚本的$1=/etc/hosts $2=/etc/passwd
# fun $2 $1 → fun /etc/passwd /etc/hosts
# 函数内$1=/etc/passwd $2=/etc/hosts
'脚本的$1和函数的$1是两个不同的东西——看谁传的'
⚠️ 函数内部$1取的是函数参数,不是脚本参数

方法2:通过赋值传参#

4)变量赋值传参
[root@shell ~]#cat fun.sh
#!/bin/bash
file=$1 # 脚本的$1赋给变量file
fun(){
if [ -f $file ];then
echo $file 存在
else
echo $file 不存在
fi
}
fun
[root@shell ~]#sh fun.sh /etc/hosts
/etc/hosts 存在
[root@shell ~]#sh fun.sh /etc/hostsssssssssssss
/etc/hostsssssssssssss 不存在
# 变量在函数外定义,函数内直接引用
'函数可以访问外部变量——通过变量桥接传参,灵活方便'

方法3:通过read读入传参#

5)read交互传参
[root@shell ~]#cat fun.sh
#!/bin/bash
read -p "请输入文件名称: " file
fun(){
if [ -f $file ];then
echo $file 存在
else
echo $file 不存在
fi
}
fun
# read读入存到file变量,函数内直接引用
传参方式特点适用场景
函数后传参 fun $1 $2位置参数,函数内用 $1 $2参数少、调用链清晰
赋值传参 file=$1变量桥接,函数内外共享参数多、多个函数共用
read读入交互式,运行时输入需要人工确认的场景

3) 函数的变量——local#

1)函数内不加local——变量泄露到外部
[root@shell ~]#cat fun.sh
#!/bin/bash
fun(){
name=oldboy
}
fun
echo $name
[root@shell ~]#sh fun.sh
oldboy
# 函数内定义的变量,函数外也能访问——变量泄露了
'不加local,函数内的变量会跑到外面去'
========================================
2)函数外加local——只在本函数内生效
========================================
[root@shell ~]#cat fun.sh
#!/bin/bash
name=oldboy
fun1(){
local name=oldgirl # local限定了作用域
}
fun1
echo $name
[root@shell ~]#sh fun.sh
oldboy
# 加了local,函数内的name=oldgirl不影响外部的name=oldboy
'local像一堵墙——函数内的变量出不去,外部的变量也进不来(同名时)'
========================================
3)函数访问外部变量——默认可以
========================================
[root@shell ~]#cat fun.sh
#!/bin/bash
name=oldboy # 外部定义
fun(){
echo $name # 函数内可以读到外部变量
}
fun
[root@shell ~]#sh fun.sh
oldboy
# 函数默认可以读取外部变量——除非函数内用local定义了同名变量

📌 核心

  • 函数默认能访问外部变量
  • local 关键字将变量作用域限定在函数内部,防止变量污染
  • 函数内不加 local 定义的变量,函数执行完后外部也能用

4) 函数的返回值#

函数通过 returnexit 自定义 $? 的值,数字范围 0-255

exit 方式#

1)exit自定义$?
[root@shell ~]#cat fun.sh
#!/bin/bash
fun1(){
if [ -f $1 ];then
exit 50
else
exit 100
fi
}
fun1 $1
[root@shell ~]#sh fun.sh /etc/hosts
[root@shell ~]#echo $?
50
[root@shell ~]#sh fun.sh /etc/hostssssssssss
[root@shell ~]#echo $?
100
# exit直接退出脚本并设置$? 文件存在→50 不存在→100
⚠️ exit会终止整个脚本,不只是退出函数

return 方式#

2)return返回$?——更推荐的方式
[root@shell ~]#cat fun.sh
#!/bin/bash
fun1(){
if [ -f $1 ];then
return 50
else
return 100
fi
}
fun1 $1
[root@shell ~]#sh fun.sh /etc/hosts
[root@shell ~]#echo $?
50
[root@shell ~]#sh fun.sh /etc/hostssss
[root@shell ~]#echo $?
100
# return只退出函数,不影响脚本后续执行
✅️ 函数内用return,脚本内用exit——各管各的

错误示范——$? 只能取一次#

3)错误写法——$?被覆盖
[root@shell ~]#cat fun.sh
#!/bin/bash
fun1(){
if [ -f $1 ];then
return 50
else
return 100
fi
}
fun1 $1
[ $? -eq 50 ] && echo 文件存在
[ $? -eq 100 ] && echo 文件不存在
# ❌ 第一个[ $? -eq 50 ]执行后$?变成了它自己的返回值(0或1)
# 第二个[ $? -eq 100 ]取到的是上一个判断的结果,不是return的值
========================================
4)正确写法——先保存$?到变量
========================================
[root@shell ~]#cat fun.sh
#!/bin/bash
fun1(){
if [ -f $1 ];then
return 50
else
return 100
fi
}
fun1 $1
re=$? # 第一时间把$?存到变量
if [ $re -eq 50 ];then
echo $1文件存在
elif [ $re -eq 100 ];then
echo $1文件不存在
fi
[root@shell ~]#sh fun.sh /etc/hosts
/etc/hosts文件存在
[root@shell ~]#sh fun.sh /etc/hostssss
/etc/hostssss文件不存在
# $?是一次性的——读到变量里保存起来,后面才能反复用
========================================
5)简化写法——用 && ||
========================================
[ $? -eq 50 ] && echo $1文件存在 || echo $1文件不存在
# 简单场景下 && || 一行搞定

📌 核心$? 每次执行命令都会更新——==拿到立刻存变量==,否则就丢了


5) 主函数#

将各个功能函数集合到 main() 中统一调度——代码结构清晰,便于维护

1)主函数调度——代码备份到远程服务器
[root@shell ~]#cat fun.sh
#!/bin/bash
code_dir=`hostname`_`hostname -I|awk '{print $1}'`_`date +%F-%H-%M-%S`
ip=172.16.1.7
# 目录名:主机名_IP_时间戳 —— 唯一标识
DIR(){
mkdir /opt/$code_dir
}
# 创建备份目录
TAR(){
cd /etc
tar zcf /opt/$code_dir/code.tar.gz hosts passwd
}
# 打包要备份的文件
COPY(){
scp -r /opt/$code_dir/ $ip:/opt/ &>/dev/null
}
# scp传送到远程服务器
UN_TAR(){
ssh $ip "cd /opt/$code_dir/ && tar xf code.tar.gz" &>/dev/null
}
# 远程解压
RM(){
ssh $ip "find /opt/ -mtime +7|xargs rm -rf" &>/dev/null
}
# 清理7天前的旧备份
main(){
DIR
TAR
COPY
UN_TAR
RM
}
main
# main按顺序调用所有函数——就像调度中心
'每个函数干一件事,main统一指挥——思路清晰,改哪里找哪里'
✅️ 主函数模式:函数拆功能 + main编排流程 = 企业级脚本结构
Tip

💡 主函数设计思想: (1)每个函数只做一件事——单一职责 (2)main 函数编排调用顺序——流程一目了然 (3)要加功能就加一个函数,在 main 里插一行——扩展成本极低


数组#

基本概念#

概念说明类比
变量一个名字对应==一个值==箱子1 = 🍎
数组一个名字对应==多个值==箱子1 = [盒子1]=🍎, [盒子2]=🍌, [盒子3]=🍐
Terminal window
# 数组的语法
数组名称[索引]=值
数组名称[下标]=值
数组名称[元素名]=值

数组分类#

类型索引方式声明
普通数组只能以数字作为索引默认即是,declare -a
关联数组可以用数字和字符串作为索引declare -A

定义数组的4种方法#

Terminal window
1)方法1:使用索引一个一个定义
[root@shell ~]#declare -a array # 声明为普通数组(默认就是)
[root@shell ~]#array[1]=shell
[root@shell ~]#array[2]=awk
[root@shell ~]#array[3]=sed
# 逐个指定索引赋值——索引可以跳号
========================================
2)方法2:直接一行定义
========================================
[root@shell ~]#unset array
[root@shell ~]#array=(awk sed grep find zabbix)
[root@shell ~]#echo ${array[*]}
awk sed grep find zabbix
[root@shell ~]#echo ${!array[*]}
0 1 2 3 4
# 括号+空格分隔,索引从0开始自动分配
'一行定义,索引自动0、1、2...排好'
========================================
3)方法3:混合定义——指定部分索引
========================================
[root@shell ~]#unset array
[root@shell ~]#array=([2]=awk sed [5]=zabbix find)
[root@shell ~]#echo ${!array[*]}
2 3 5 6
[root@shell ~]#echo ${array[*]}
awk sed zabbix find
[root@shell ~]#echo ${array[2]}
awk
[root@shell ~]#echo ${array[3]}
sed
[root@shell ~]#echo ${array[5]}
zabbix
[root@shell ~]#echo ${array[6]}
find
# [2]=awk 索引2赋值awk, sed紧跟索引3
# [5]=zabbix 索引5赋值zabbix, find紧跟索引6
'指定索引的那个位置开始,后面依次排——没指定的索引就跳过'
========================================
4)方法4:命令结果赋值给数组
========================================
[root@shell ~]#unset array
[root@shell ~]#array=(`ls`)
[root@shell ~]#ls
1.txt c.sh count.sh day01-day03 for.sh fun.sh jumpserver.sh pass.txt ping.sh test.sh user.sh weixin.py while.sh
[root@shell ~]#echo ${array[*]}
1.txt count.sh c.sh day01-day03 for.sh fun.sh jumpserver.sh pass.txt ping.sh test.sh user.sh weixin.py while.sh
[root@shell ~]#echo ${!array[*]}
0 1 2 3 4 5 6 7 8 9 10 11 12
# ls的输出按空格分隔,自动变成数组元素
'命令结果存数组——方便后面遍历处理'

查看数组#

Terminal window
[root@shell ~]#echo ${array[1]} # 查看索引1的值
shell
[root@shell ~]#echo ${array[2]}
awk
[root@shell ~]#echo ${array[3]}
sed
[root@shell ~]#echo ${array[*]} # 查看所有值(作为一个整体)
shell awk sed
[root@shell ~]#echo ${array[@]} # 查看所有值(作为独立元素)
shell awk sed
[root@shell ~]#echo ${!array[*]} # 查看所有索引
1 2 3
语法含义
${array[N]}取索引 N 的值
${array[*]}取所有值,当整体
${array[@]}取所有值,每个独立
${!array[*]}取所有索引名

案例1:数组 + for 探测不连续IP#

数组天然适合存储不规则的地址列表,配合 for 遍历

1)遍历数组值——${ip[*]}
[root@shell ~]#cat ra.sh
#!/bin/bash
ip=(
www.baidu.com
10.0.0.2
www.sina.com
www.baiduddd.com
10.0.0.7
10.0.0.51
)
for i in ${ip[*]} # 遍历数组的所有值
do
ping -c1 -W1 $i &>/dev/null
[ $? -eq 0 ] && echo $i 在线....
done
[root@shell ~]#sh ra.sh
www.baidu.com 在线....
10.0.0.2 在线....
www.sina.com 在线....
10.0.0.7 在线....
# 域名和IP混在一起也能遍历——数组不要求元素类型一致
'${ip[*]}遍历值——取到的是什么就ping什么'

案例2:通过索引遍历#

2)先看索引和值的对应关系
[root@shell ~]#cat ra.sh
#!/bin/bash
ip=(
www.baidu.com
10.0.0.2
www.sina.com
www.baiduddd.com
10.0.0.7
10.0.0.51
)
for i in ${!ip[*]} # 遍历索引: 0 1 2 3 4 5
do
echo $i ${ip[$i]} # 索引 + 值
done
[root@shell ~]#sh ra.sh
0 www.baidu.com
1 10.0.0.2
2 www.sina.com
3 www.baiduddd.com
4 10.0.0.7
5 10.0.0.51
========================================
3)通过索引ping
========================================
[root@shell ~]#cat ra.sh
#!/bin/bash
ip=(...同上...)
for i in ${!ip[*]}
do
ping -c1 -W1 ${ip[$i]} &>/dev/null
[ $? -eq 0 ] && echo ${ip[$i]} 在线....
done
[root@shell ~]#sh ra.sh
www.baidu.com 在线....
10.0.0.2 在线....
www.sina.com 在线....
10.0.0.7 在线....
'${!ip[*]}遍历索引→${ip[$i]}取对应值——效果一样,多了一步'

📌 ${ip[*]} 直接遍历值 vs ${!ip[*]} 先取索引再取值——大多数场景前者就够了,需要知道位置时用后者


关联数组#

关联数组可以用==字符串作为索引==,定义前必须先 declare -A 声明

Terminal window
1)声明并赋值
[root@shell ~]#declare -A array
[root@shell ~]#array[index1]=awk
[root@shell ~]#array[index2]=sed
[root@shell ~]#array[index3]=grep
[root@shell ~]#echo ${array[*]}
awk grep sed
[root@shell ~]#echo ${!array[*]}
index1 index3 index2
# 索引是字符串,不是数字——这是关联数组和普通数组的唯二区别
'declare -A 是关键——不声明就当普通数组用,字符串索引会被当成0'

案例3:统计性别出现次数#

关联数组的经典应用:==按类别统计计数==

Terminal window
1)准备数据
[root@shell ~]#cat sex.txt
m
m
f
m
f
x
[root@shell ~]#cat sex.txt|sort |uniq -c
2 f
3 m
1 x
# 传统方式用sort|uniq -c统计,但这是外部命令
========================================
2)关联数组统计——纯bash实现
========================================
[root@shell ~]#cat sex.sh
#!/bin/bash
while read line
do
declare -A sex
let sex[$line]++ # 以$line为key,计数+1
done<sex.txt
for i in ${!sex[*]}
do
echo $i 出现了 ${sex[$i]}
done
[root@shell ~]#sh sex.sh
x 出现了 1
m 出现了 3
f 出现了 2
# 每读一行,sex[m]++ 或 sex[f]++,最后遍历输出
'let sex[$line]++ 是精髓——key自动分组,值自动累加'
❤️ 关联数组做统计:不用sort、不用uniq、纯bash搞定

案例4:统计系统中每种shell的使用人数#

3)统计/etc/passwd中每种shell的用户数
[root@shell ~]#cat sex.sh
#!/bin/bash
while read line
do
declare -A shell
let shell[`echo $line|awk -F: '{print $NF}'`]++
# 取每行最后一个字段(shell类型),以此为key计数
done</etc/passwd
for i in ${!shell[*]}
do
echo $i 出现了 ${shell[$i]}
done
[root@shell ~]#sh sex.sh
/bin/bash 出现了 11
/bin/false 出现了 1
/bin/sync 出现了 1
/sbin/halt 出现了 1
/sbin/nologin 出现了 32
/sbin/shutdown 出现了 1
# /etc/passwd最后一段是用户登录shell
# /sbin/nologin最多——说明大部分是服务账户,不能登录
'一行命令看出系统账户分布——关联数组做分类统计就是这么直接'

📌 关联数组统计模式

Terminal window
while read line
do
declare -A 数组名
let 数组名[分类字段]++
done<数据文件
# 然后遍历${!数组名[*]}输出结果


小结#

知识点核心要点
函数定义三种写法:fun(){} / function fun(){} / function fun{}
函数传参三种方式:fun $1 $2 / 赋值传参 file=$1 / read 交互
local限定变量作用域在函数内,防止变量污染外部
return vs exitreturn 退出函数,exit 退出脚本;$? 范围都是 0-255
$? 保存$? 是一次性的——拿到立刻 re=$? 存变量
主函数 main()函数拆功能 + main 编排流程 = 企业级脚本骨架
普通数组array=(值1 值2),数字索引,默认从0开始
关联数组declare -A array,字符串索引,用前必须声明
${array[*]}查看数组所有值
${!array[*]}查看数组所有索引
let array[key]++关联数组做分组统计的核心操作
Important

📌 函数核心价值:去重 + 封装 + 可维护——重复代码抽函数,一个函数干一件事

📌 数组核心价值:一个变量存多个值 + 支持遍历 + 关联数组天然做分组统计

📌 Shell编程完整路线:变量 → 判断 → if → case → for → while → 函数 → 数组

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

函数与数组
https://www.kpyun.fun/posts/automation/shell/shell04/
作者
久棹
发布于
2026-07-02
许可协议
CC BY-NC-SA 4.0
Profile Image of the Author
久棹
只要胆子大,天天寒暑假!
公告
欢迎来到久棹的技术小站!本站专注 Linux 运维学习笔记分享,如有问题欢迎交流探讨 🎉
分类
标签
站点统计
文章
98
分类
11
标签
203
总字数
244,453
运行时长
0
最后活动
0 天前
站点信息
构建平台
Local
博客版本
Firefly v6.13.5
文章许可
CC BY-NC-SA 4.0

文章目录