shell 数组的详细用法
学习shell数组从简单到高级的用法
简介
数组是一种数据结构,用于存储和处理一组相关的数据元素。数组可以包含多个值,每个值都有一个索引,用于标识和访问它们。
目录
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") )
如果要处理嵌套或多维数组,通常需要创建多个数组来表示子数组或多维数组。在处理大型、多维或嵌套数组时,可能会占用大量的内存。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)