简介

    数组是一种数据结构,用于存储和处理一组相关的数据元素。数组可以包含多个值,每个值都有一个索引,用于标识和访问它们。

        

目录

1. 数组的基本用法

1.1. 定义数组的方式

1.1.1. 直接赋值

1.1.2. declare声明数组

1.1.3. 索引赋值

1.1.4. 命令赋值

1.2. 更新数组的方式

1.2.1. 增加数组元素

1.2.2. 修改数组元素

1.2.3. 删除数组元素

1.2.4. 查询数组元素

1.3. 索引切片

1.4. 字母大小写转换

2. 数组的应用

2.1. 连续处理函数

2.2. 定时判断文件大小

2.3. 注意事项


        

1. 数组的基本用法

定义数组的方式类似于定义变量,比如我们定义一个普通变量是这样的:

v="abc"
v=123
v=12ab

数组就是在等于符号后面加上括号,例如:

v=(a b c)
v=(1 2 3)

注意:分隔符默认是空格,这个空格可以是多个空格,也可以tab,只要中间有间隔即可。

         

数组相比于普通变量,数组能存储和访问多个值,在使用过程中也更加的灵活。例如:使用数组和普通变量赋值一个相同的值

variable="Hello World"
array=(Hello World)

普通变量输出方式:

数组可以输出单个值或全部值,这就是数组的优势。

        

1.1. 定义数组的方式

1.1.1. 直接赋值

赋值的对象可以是字符串,可以是数字,也可以是它们的组合,例如:

v=('a' 'b' 'c')
v=(1 2 3)
v=('a' 'b' 1 2)

除了直接赋值,我们也可以通过其他的变量赋值,例如:

n=10
v=('a' 'b' ${n})

         

注意:shell中的数组不涉及深拷贝、浅拷贝的概念,拷贝的方式是对整个数组最新的值进行拷贝。

# 定义2个数组
arr1=(10 20 30)
arr2=('a' 'b' ${arr1[@]})

# 输出这两个数组的结果
echo "两个数组原本的结果:arr1=${arr1[@]}  arr2=${arr2[@]}"

# 修改被拷贝的数组arr1
arr1[1]=50

# 再次输出这两个数组的结果
echo "arr1被修改后的结果:arr1=${arr1[@]}  arr2=${arr2[@]}"

当我们对数组 arr2 赋值数组 arr1 的值后,再修改数组 arr1,这时数组 arr2 也不会发生改变。

如果我们在将 arr1 赋值给 arr2 之前修改值,则 arr2 使用最新的值。例如:

arr1=(10 20 30)

# 修改数组arr1的值
arr1[1]=50

arr2=('a' 'b' ${arr1[@]})

# 输出这两个数组的结果
echo "arr1: ${arr1[@]}"
echo "arr2: ${arr2[@]}"

        

1.1.2. declare声明数组

declare的参数选项

-a:将变量声明为普通数组。
-A:将变量声明为关联数组。
-r:将变量声明为只读(不可修改)。

-i:将变量声明为整数。
-l:将变量的值转换为小写。    #不支持数组
-u:将变量的值转换为大写。    #不支持数组

-f:显示指定函数名的定义,如果不指定函数名,则显示所有函数的定义。用于debug调试
-g:将变量声明为全局变量,使其在函数内外都可访问。
-x:将变量声明为环境变量,可以在 Shell 进程及其子进程中访问。
-x <value>:将变量声明为环境变量,并设置默认值。

        

普通数组和关联数组分别由 -a 和 -A 指定,它们的区别是普通数组使用整数作为索引号,关联数组使用字符串作为索引号(以键值对的方式取值)。例如:

# 普通数组
declare -a arr[0]='abc'
# 关联数组
declare -A arr[a]='abc'

系统默认是普通数组,所以一般不需要使用 -a 指定。关联数组属于特殊数组,需要使用 -A 指定,也可以这样写:

declare -A arr
arr[a1]='abc'
arr[a2]='def'

或者这样写

declare -A arr=([a1]='BBB' [a2]='CCC')

注意:=() 这种方式不能放在后面,否则前面插入的值会被覆盖。

【案例一】先使用索引方式增加值,再使用第2种 =() 的方式

# 定义关联数组
declare -A arr
# 使用索引方式赋值
arr[a1]='abc'
arr[a2]='def'
# 使用括号+键值对方式赋值
arr=([a3]='BBB' [a4]='CCC')
# 输出结果
echo ${arr[@]}

 

a1、a2的值被覆盖

【案例二】我们换一下顺序,将单独插入索引值放在后面

# 定义关联数组
declare -A arr
# 使用括号+键值对方式赋值
arr=([a3]='BBB' [a4]='CCC')
# 使用索引方式赋值
arr[a1]='abc'
arr[a2]='def'
# 输出结果
echo ${arr[@]}

此时前面的值不会被覆盖 

        

注意:当使用 ${arr[@]} 输出整个关联数组时,它会按照 bash 的内部实现来确定元素的顺序,而不是按照插入元素的顺序。

当然了,在实际的用法中我们需要按顺序输出时,一般都是使用普通数组,关联数组一般是用于一些特定的场景,所以不需要关心它的顺序。但是如果这个数组不固定,可能有很多地方存在增加或删除的情况,那么我们需要知道当前数组的键和值如何获取

所有键:${!arr[@]}
所有值:${arr[@]}

例如:

# 定义关联数组
declare -A arr=([a1]='BBB' [a2]='CCC')

# 输出键和值
echo "数组arr的所有键: ${!arr[@]}"
echo "数组arr的所有值: ${arr[@]}"

        

除了声明数组的类型外,还有一些整数的声明方式,例如:

declare -i arr=('abc' 1 2)
echo ${arr[@]}

我们在定义这个变量的时候直接在数组中写入一个字符串,然后得到的结果就是:abc没有被改变 

如果我们写成这样,字符串abc就可以被转换为数字0

declare -i arr
arr=('abc' 1 2)
echo ${arr[@]}

        

我们可以使用declare声明数组整数,却只能声明字符串变量的大小写,不支持数组。这里举2个普通变量的声明大小写方式:

# 声明为小写
declare -l var
var='abC'
echo ${var}

# 声明为大写
declare -u var
var='abC'
echo ${var}

注意:脚本中必须在变量赋值之前声明,如果是在赋值之后声明是无效的。

var='abC'
# 声明为大写
declare -u var
echo ${var}

但如果直接在命令操作的话是有效的

《如果需要将数组的字符串转换为大小写需要将其遍历出来修改。方法见目录2》

        

1.1.3. 索引赋值

默认情况下数组是通过整数索引来访问元素,范围是:0~32767。普通数组不同于关联数组,关联数组使用字符的方式赋值,所以输出整个数组的值对我们来说可能非顺序的;而普通数组输出整个数组时是有序的。例如:

arr=(abc 000 def 123)
echo ${arr[@]}

        

当我们需要对某个位置插入值时,如果这个位置已经有值则直接修改,如果没有则插入。

arr=(abc 000 def 123)
echo "arr原始数据: ${arr[@]}"

# 在索引为1的位置插入或修改值
arr[1]=111
echo "arr修改索引: ${arr[@]}"

        

当代码太多,不知道数组有多少时,我们可以通过追加的方式插入数据

arr=(abc 000 def 123)
echo "原始数据: ${arr[@]}"

# 向数组arr追加元素
arr+=("value1")
arr+=("value2" "value3")
echo "增加数据: ${arr[@]}"

        

1.1.4. 命令赋值

除了手动赋值以外,我们还可以通过Linux命令、函数等方式对数组赋值。

【案例一】直接执行Linux命令对数组赋值

arr=(`ls /home/yt/shell/file`)
echo ${arr[@]}

如果数组中已有数据,那么通过追加的方式赋值

# 定义一些元素到数组
arr=(abc 000)
echo "old_arr: ${arr[@]}"

# 向数组arr追加元素
arr+=(`ls /home/yt/shell/file`)
echo "new_arr: ${arr[@]}"

        

【案例二】通过读取交互输入的值增加到数组

# 定义数组
declare -a arr
# 循环读取交互的值
while read -r line; do
        [ "${line}" == "exit" ] && break
        arr+=("$line")
done
# 输出最终结果
echo "result: ${arr[@]}"

        

【案例三】通过函数结果赋值

# 定义一个函数
func1(){
        echo "123 abc"
        }
# 将函数输出的结果赋值到数组
arr=(`func1`)
# 输出结果
echo "全部的值 : ${arr[@]}"
echo "索引0的值: ${arr[0]}"

对函数来说输出的 "123 abc" 是一个字符串,但中间有一个空格,所以对数组来说是2个值

如果在执行函数加上双引号则对数组来说就是一个值

arr=("`func1`")

注意:函数的返回值无法赋值到数组

​# 定义一个函数
func1(){
        return 1
        }
# 将函数输出的结果赋值到数组
arr=(`func1`)
# 输出结果
echo ${arr[@]}

        

1.2. 更新数组的方式

1.2.1. 增加数组元素

在原数组最后面追加元素

# 原数组
arr=(a b c)
# 向数组中追加新的元素
arr+=(1 2 3)
# 查看结果
echo ${arr[@]}

        
在原数组最前面增加n个元素

# 原数组
arr=(a b c)
# 重新定义数组,在原数组前方增加n个元素
arr=("123" ${arr[@]})
# 查看结果
echo ${arr[@]}

        

按索引增加数组

# 原数组
arr=(a b c)
# 向数组中追加新的元素
arr[3]="d"
# 查看结果
echo ${arr[@]}

        

在索引0和1之间增加一个元素

# 原数组
arr=(1 2 3)
# 切片前半部分和后半部分,然后与新元素进行拼接
arr=(${arr[@]:0:1} "abc" ${arr[@]:1})
# 输出结果
echo ${arr[@]}


         

1.2.2. 修改数组元素

按索引修改元素

# 原数组
arr=(1 2 3)
# 将索引为0的元素修改为100
arr[0]=100
# 输出结果
echo ${arr[@]}

        

按值修改(将元素为3的修改为30)

# 定义一个数组
arr=(1 2 3 4 5)

# 定义需要修改的值和新值
old_value=3
new_value=30

# 遍历数组索引
for i in ${!arr[@]}; do
    # 通过索引查看值,判断该是否为需要修改的值,如果是则修改
	[ "${arr[$i]}" == "${old_value}" ] && arr[$i]=${new_value}
done

# 输出修改后的数组
echo "${arr[@]}"

        

1.2.3. 删除数组元素

  • 删除元素我们通过 unset 去操作
unset 数组名        #删除整个数组
unset 数组名[索引]  #删除数组的某个索引

        

删除整个数组

# 定义一个数组
arr=(1 2 3 4 5)
# 删除这个数组
unset arr
# 输出结果
echo ${arr[@]}

        

按索引删除

# 定义一个数组
arr=(1 2 3 4 5)
# 删除索引为0的元素
unset arr[0]
# 输出结果
echo ${arr[@]}

注意:unset不支持索引切片删除

        

按值删除

# 定义一个数组
arr=(1 2 3 2 4)
# 需要删除的值
unset_value=2

# 便利数组的索引
for i in "${!arr[@]}"; do
    # 如果该索引的值==需要删除的值,则删除
    [ "${arr[$i]}" == "${unset_value}" ] && unset arr[$i]
done

# 输出结果
echo ${arr[@]}

        

1.2.4. 查询数组元素

查询数组的全部元素

echo ${arr[@]}

        

按索引查询某个元素

echo ${arr[0]}    #查看索引为0的元素
echo ${arr[1]}    #查看索引为1的元素

        

查看元素的长度

echo ${#arr[@]}

        

查看元素的索引(从0开始)

echo ${!arr[@]}

        

 过滤出长度为3的元素

arr=("VVVV" "abc" 124 "AA" "===" "ddd")
echo ${arr[@]} |xargs -n 1 |awk 'length($0)==3'

        

1.3. 索引切片

切片的语法如下

${arr[@]:开始索引:向后次数}

查看索引为2后面的全部元素(包括2)

${arr[@]:2}

        

查看索引为2后面3个元素

${arr[@]:2:3}

        

查看前面4个元素

${arr[@]:0:4}

        

查看后面4个元素

${arr[@]:(-4)}

         

查看中间 2~5 元素

${arr[@]:1:4}

        

1.4. 字母大小写转换

 转换大写

arr=("abC" "Def" 1 2 3)

# 使用循环遍历数组,并将每个元素转换为大写
for ((i=0; i<${#arr[@]}; i++)); do
        arr[$i]=${arr[$i]^^}
done

echo "${arr[@]}"

        

转换小写

arr=("abC" "Def" 1 2 3)

# 使用循环遍历数组,并将每个元素转换为小写
for ((i=0; i<${#arr[@]}; i++)); do
        arr[$i]=${arr[$i],,}
done

echo "${arr[@]}"

        

每个单词首字母大写,后面的小写

arr=("abC" "Def" 1 2 3)

# 使用循环遍历数组
for ((i=0; i<${#arr[@]}; i++)); do
    # 读取第1个字符
	firstChar="${arr[$i]:0:1}"
    # 读取第1个以后的字符
	remainingChars="${arr[$i]:1}"
    # 将第一个字符转换为大小,后面的转换为小写
	arr[$i]="${firstChar^^}${remainingChars,,}"
done

echo "${arr[@]}"

        

2. 数组的应用

2.1. 连续处理函数

先来看一个简单的例子

#!/bin/bash

arr=(1 2 3 4 5)

func1() {
    # 参数个数大于0则一直循环
    while [[ $# -gt 0 ]]; do
        local arg="$1"
        if [ ${arg} -eq 1 ];then
            # 在这里添加处理逻辑
            echo "执行任务 1"
        elif [ ${arg} -eq 2 ];then
            # 在这里添加处理逻辑
            echo "执行任务 2"
        elif [ ${arg} -eq 3 ];then
            # 在这里添加处理逻辑
            echo "执行任务 3"
        fi
        shift # 将头部的参数删除
    done
    }
func1 ${arr[@]}

我们通过数组定义了一组值,将这组值传入函数中,函数根据值判断是否执行某个任务。

        

上述可能看不出实际有什么作用,我们带入一个简单的场景,需求如下:

  • 使用最小的代码量封装一个函数用于操作文件,对函数传入的参数顺序执行
#!/bin/bash

arr=(`ls /home/yt/shell/file/*.txt`)

func1() {
    # 参数个数大于0则一直循环
    while [[ $# -gt 0 ]]; do
        local arg="$1"
        if [ -f ${arg} ];then
            # 操作这个文件,例如追加或修改数据
            echo "test" >>${arg}
        fi
        shift # 将头部的参数删除
    done
    }
func1 ${arr[@]}

由于数组可以存储的东西很多,并且在使用的时候我们可以选择式的调用,相对比较方便。

        

2.2. 定时判断文件大小

需求:不确定某个目录下的文件,有时增加、有时删除,需要对所有文件进行监控大小,发生变化后及时提醒。

#!/bin/bash

# 配置需要检查文件的目录
file_path="/home/yt/shell/file"

while true;do
    # 声明一个关联数组
    declare -A arr
    # 将目录下的所有文件放入一个变量
    files="$(find ${file_path} -type f)"

    # 遍历需要检查的文件
    for file in ${files};do
        # 将这些文件大小存储到关联数组中
        arr["${file}"]=$(du -b ${file} |awk '{print $1}')
    done

    # 休眠60s后再次读取文件大小
    sleep 60
    for file in ${files};do
        # 读取文件大小
        size=$(du -b ${file} |awk '{print $1}')
        # 判断与1s前,大小是否发生变化
        if [ ${arr["${file}"]} -ne ${size} ];then
            echo -e "[`date '+%Y-%m-%d %H:%M:%S'`] 文件${file}大小发生变化, 当前${size}byte"
        fi
    done
done

根据需求我们编写一个循环,在循环体中按指定间隔时间不断的的查看文件大小,对比两次查看的大小是否一致。如果文件大小一致则不用预警,如果文件大小不一致则输出预警。

        

2.3. 注意事项

数组在日常中用的比较多,但有些操作可能导致内存增加,比如:

【对大型数组进行排序、过滤、转换等】

array=("大型数组")
# 排序
sorted_array=($(echo "${array[@]}" | tr ' ' '\n' | sort))

如果要对数组进行排序,通常需要将其复制到一个临时数组中,然后对临时数组进行排序,最后将结果复制回原始数组。在排序或复制大型数组时,可能会分配大量的内存。

        

【创建嵌套或多维数组】

arr=( ("数组1") ("数组2") ("数组3") )

如果要处理嵌套或多维数组,通常需要创建多个数组来表示子数组或多维数组。在处理大型、多维或嵌套数组时,可能会占用大量的内存。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐