Linux Shell编程及自动化运维实现-4-数组与函数

变量能代替一个不固定的字符串,但如果有 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       # 删除整个数组

breakcontinue 的区别

命令效果
continue跳过本次循环剩余代码,继续下一次循环
break结束整个循环
break n跳出 n 层嵌套循环

shift 可以移动多位吗?

可以。shift 2 表示位置参数向左移动 2 位。

局部变量 local 的作用

myfunc() {
    local var="local value"
    echo $var
}

local 声明的变量仅在函数内部有效,不会影响外部同名变量。


最佳实践

  1. 使用关联数组做键值映射


    declare -A config
    config[host]="localhost"
    config[port]="3306"

  2. 使用数组做数据统计


    declare -A count
    while read line; do
    let count[$line]++
    done < data.txt

  3. 函数传参使用 $@


    myfunc() {
    for arg in "$@"; do
    echo "处理: $arg"
    done
    }

  4. 函数内使用 local 变量


    myfunc() {
    local result
    result=$(some_command)
    echo $result
    }

  5. shift 遍历所有参数


    while [ $# -ne 0 ]; do
    process $1
    shift
    done


总结

通过本章节的学习,你应该掌握了:

  • 理解数组的概念和两种类型(普通数组、关联数组)
  • 掌握普通数组的定义方法(一次赋一个值、一次赋多个值)
  • 掌握数组的访问操作(单个元素、所有元素、统计个数、获取索引、切片)
  • 掌握关联数组的声明和使用(declare -A
  • 掌握 while/for 循环快速定义数组的方法
  • 掌握使用数组进行数据统计(性别统计、Shell 类型统计)
  • 理解函数的概念和作用(代码模块化、复用)
  • 掌握函数的两种定义方法和调用方式
  • 掌握函数传参($1$2$@$*
  • 掌握局部变量 local 的使用
  • 掌握数组与函数的结合使用(数组传参、函数结果赋予数组)
  • 掌握内置控制命令(break、continue、shift)
  • 理解嵌套循环的执行规则

学习建议:数组和函数是 Shell 编程进阶的核心。建议在虚拟机上动手练习每一个案例,特别是数组统计和函数传参。数组结合关联数组做数据统计是非常实用的技能,函数结合数组可以实现更复杂的运维自动化任务。


🙋 学习路上不孤单,有坑一起填!



卡在参数调试、命令打架、功能没反应、资源失效?其他问题?直接戳我,也可以进群和同好交流!



📱 个人QQ:3838586495    👥 交流Q群:1094091455    📮 邮箱:leyan2504@163.com



内容帮你省了大把踩坑时间?可以文末小小打赏鼓励一波,也欢迎安利给身边学习的朋友~



✨ 欢迎来评论区「交作业」:分享你的配置思路、提出疑难问题;评论区正确填写邮箱,不错过任何人的解答回复!


暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇