函数与数组
3480 字
17 分钟
函数与数组
函数 && 数组
[TOC]
函数
基本概念
变量是一个名字对应一个值,==函数是一个名字对应一堆命令==——把完成特定功能的代码块打个包、起个名,需要时喊名字就行
# 函数只定义、不调用则不执行函数名(){ 命令集}# 函数必须先定义再调用# 函数名不能和系统命令重名Important
📌 ==变量==是一个名字对应一个值 📌 ==函数==是一个名字对应一堆命令
把重复的活儿打包成函数,一次定义,到处调用
1) 函数定义的三种方式
[root@shell ~]#cat fun.sh#!/bin/bashfun1(){ echo "第一种定义方法"}# 最常用写法——函数名+()+{}
function fun2(){ echo "第二种定义方法"}# 加function关键字,更像其他语言
function fun3 { echo "第三种定义方式"}# function + 函数名 + {命令},省略括号
fun1fun2fun3# 调用函数——喊名字就执行里面的全部命令'三种写法等效,第一种最简洁,也是最常用的'2) 函数的传参
函数传参有三种方式——和脚本传参是同一套逻辑
方法1:在函数后传参(位置参数)
1)传一个参数——$1[root@shell ~]#cat fun.sh#!/bin/bashfun(){ 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/bashfun(){ 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/bashfun(){ 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/bashfile=$1 # 脚本的$1赋给变量filefun(){ 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/bashread -p "请输入文件名称: " filefun(){ 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/bashfun(){ name=oldboy}funecho $name[root@shell ~]#sh fun.sholdboy# 函数内定义的变量,函数外也能访问——变量泄露了'不加local,函数内的变量会跑到外面去'
========================================2)函数外加local——只在本函数内生效========================================[root@shell ~]#cat fun.sh#!/bin/bashname=oldboyfun1(){ local name=oldgirl # local限定了作用域}fun1echo $name[root@shell ~]#sh fun.sholdboy# 加了local,函数内的name=oldgirl不影响外部的name=oldboy'local像一堵墙——函数内的变量出不去,外部的变量也进不来(同名时)'
========================================3)函数访问外部变量——默认可以========================================[root@shell ~]#cat fun.sh#!/bin/bashname=oldboy # 外部定义fun(){ echo $name # 函数内可以读到外部变量}fun[root@shell ~]#sh fun.sholdboy# 函数默认可以读取外部变量——除非函数内用local定义了同名变量📌 核心:
- 函数默认能访问外部变量
local关键字将变量作用域限定在函数内部,防止变量污染- 函数内不加
local定义的变量,函数执行完后外部也能用
4) 函数的返回值
函数通过 return 或 exit 自定义 $? 的值,数字范围 0-255
exit 方式
1)exit自定义$?[root@shell ~]#cat fun.sh#!/bin/bashfun1(){ 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/bashfun1(){ 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/bashfun1(){ 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/bashfun1(){ if [ -f $1 ];then return 50 else return 100 fi}fun1 $1re=$? # 第一时间把$?存到变量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/bashcode_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]=🍐 |
# 数组的语法数组名称[索引]=值数组名称[下标]=值数组名称[元素名]=值数组分类
| 类型 | 索引方式 | 声明 |
|---|---|---|
| 普通数组 | 只能以数字作为索引 | 默认即是,declare -a |
| 关联数组 | 可以用数字和字符串作为索引 | declare -A |
定义数组的4种方法
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 ~]#ls1.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的输出按空格分隔,自动变成数组元素'命令结果存数组——方便后面遍历处理'查看数组
[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/baship=( 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.shwww.baidu.com 在线....10.0.0.2 在线....www.sina.com 在线....10.0.0.7 在线....# 域名和IP混在一起也能遍历——数组不要求元素类型一致'${ip[*]}遍历值——取到的是什么就ping什么'案例2:通过索引遍历
2)先看索引和值的对应关系[root@shell ~]#cat ra.sh#!/bin/baship=( 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 5do echo $i ${ip[$i]} # 索引 + 值done[root@shell ~]#sh ra.sh0 www.baidu.com1 10.0.0.22 www.sina.com3 www.baiduddd.com4 10.0.0.75 10.0.0.51
========================================3)通过索引ping========================================[root@shell ~]#cat ra.sh#!/bin/baship=(...同上...)
for i in ${!ip[*]}do ping -c1 -W1 ${ip[$i]} &>/dev/null [ $? -eq 0 ] && echo ${ip[$i]} 在线....done[root@shell ~]#sh ra.shwww.baidu.com 在线....10.0.0.2 在线....www.sina.com 在线....10.0.0.7 在线....'${!ip[*]}遍历索引→${ip[$i]}取对应值——效果一样,多了一步'📌 ${ip[*]} 直接遍历值 vs ${!ip[*]} 先取索引再取值——大多数场景前者就够了,需要知道位置时用后者
关联数组
关联数组可以用==字符串作为索引==,定义前必须先 declare -A 声明
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:统计性别出现次数
关联数组的经典应用:==按类别统计计数==
1)准备数据[root@shell ~]#cat sex.txtmmfmfx[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/bashwhile read linedo declare -A sex let sex[$line]++ # 以$line为key,计数+1done<sex.txt
for i in ${!sex[*]}do echo $i 出现了 ${sex[$i]} 次done[root@shell ~]#sh sex.shx 出现了 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/bashwhile read linedo 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最多——说明大部分是服务账户,不能登录'一行命令看出系统账户分布——关联数组做分类统计就是这么直接'📌 关联数组统计模式:
while read linedo declare -A 数组名 let 数组名[分类字段]++done<数据文件# 然后遍历${!数组名[*]}输出结果小结
| 知识点 | 核心要点 |
|---|---|
| 函数定义 | 三种写法:fun(){} / function fun(){} / function fun{} |
| 函数传参 | 三种方式:fun $1 $2 / 赋值传参 file=$1 / read 交互 |
local | 限定变量作用域在函数内,防止变量污染外部 |
return vs exit | return 退出函数,exit 退出脚本;$? 范围都是 0-255 |
$? 保存 | $? 是一次性的——拿到立刻 re=$? 存变量 |
主函数 main() | 函数拆功能 + main 编排流程 = 企业级脚本骨架 |
| 普通数组 | array=(值1 值2),数字索引,默认从0开始 |
| 关联数组 | declare -A array,字符串索引,用前必须声明 |
${array[*]} | 查看数组所有值 |
${!array[*]} | 查看数组所有索引 |
let array[key]++ | 关联数组做分组统计的核心操作 |
Important
📌 函数核心价值:去重 + 封装 + 可维护——重复代码抽函数,一个函数干一件事
📌 数组核心价值:一个变量存多个值 + 支持遍历 + 关联数组天然做分组统计
📌 Shell编程完整路线:变量 → 判断 → if → case → for → while → 函数 → 数组
文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!
相关文章智能推荐
1
循环与case多分支
Shell脚本Shell循环结构与case多分支语句,涵盖for/while/until循环及实战脚本
2
数值运算与if条件判断
Shell脚本Shell数值运算方法与if条件判断结构,涵盖整数/字符串比较及文件测试
3
Shell编程基础
Shell脚本Shell脚本基础入门,涵盖变量、引号规则、条件测试及脚本调试方法
4
nmcli&&bond接口绑定
Linux扩展基础掌握nmcli网络管理与Bond接口绑定,涵盖主备/轮询模式、Bridge组合及故障切换机制
5
Ansible Docker 动态清单
Ansible自动化讲解Ansible与Docker集成及动态清单机制,涵盖容器化部署与inventory动态管理
随机文章随机推荐




