数组

数组不是 Shell 的强项,但用好了能简化很多逻辑。Bash 支持普通数组和关联数组,基本够用。

普通数组

定义数组

# 方式一:括号定义
FRUITS=("apple" "banana" "orange")

# 方式二:逐个赋值
FRUITS[0]="apple"
FRUITS[1]="banana"
FRUITS[2]="orange"

# 方式三:declare 声明
declare -a FRUITS
FRUITS=("apple" "banana" "orange")

访问元素

FRUITS=("apple" "banana" "orange")

# 访问单个元素(索引从0开始)
echo ${FRUITS[0]}    # apple
echo ${FRUITS[1]}    # banana

# 访问所有元素
echo ${FRUITS[@]}    # apple banana orange
echo ${FRUITS[*]}    # apple banana orange

# 访问索引
echo ${!FRUITS[@]}   # 0 1 2

# 数组长度
echo ${#FRUITS[@]}   # 3

# 单个元素长度
echo ${#FRUITS[0]}   # 5

添加元素

FRUITS=("apple" "banana")

# 追加元素
FRUITS+=("orange")
FRUITS+=("grape" "mango")

# 指定索引添加
FRUITS[5]="kiwi"    # 索引可以不连续

echo ${FRUITS[@]}   # apple banana orange grape mango kiwi
echo ${#FRUITS[@]}  # 6

删除元素

FRUITS=("apple" "banana" "orange")

# 删除单个元素
unset FRUITS[1]
echo ${FRUITS[@]}   # apple orange

# 删除整个数组
unset FRUITS

数组切片

FRUITS=("apple" "banana" "orange" "grape" "mango")

# 从索引1开始,取3个元素
echo ${FRUITS[@]:1:3}    # banana orange grape

# 从索引2开始,取到最后
echo ${FRUITS[@]:2}      # orange grape mango

遍历数组

遍历元素

FRUITS=("apple" "banana" "orange")

# 方式一
for FRUIT in ${FRUITS[@]}; do
    echo $FRUIT
done

# 方式二(推荐,处理带空格的元素)
for FRUIT in "${FRUITS[@]}"; do
    echo $FRUIT
done

# 方式三:带索引
for I in ${!FRUITS[@]}; do
    echo "$I: ${FRUITS[$I]}"
done

C 风格遍历

FRUITS=("apple" "banana" "orange")
LEN=${#FRUITS[@]}

for ((I=0; I<LEN; I++)); do
    echo "${FRUITS[$I]}"
done

关联数组

关联数组就是键值对,类似其他语言的 Map 或字典。

定义关联数组

# 必须先声明
declare -A USER

USER[name]="张三"
USER[age]=25
USER[city]="北京"

# 或一次性定义
declare -A USER=(
    [name]="张三"
    [age]=25
    [city]="北京"
)

访问和操作

# 访问元素
echo ${USER[name]}    # 张三

# 所有值
echo ${USER[@]}       # 张三 25 北京

# 所有键
echo ${!USER[@]}      # name age city

# 元素个数
echo ${#USER[@]}      # 3

# 判断键是否存在
if [[ -v USER[name] ]]; then
    echo "name 存在"
fi

遍历关联数组

declare -A USER=(
    [name]="张三"
    [age]=25
    [city]="北京"
)

# 遍历键
for KEY in ${!USER[@]}; do
    echo "$KEY: ${USER[$KEY]}"
done

# 输出类似:
# name: 张三
# age: 25
# city: 北京

数组操作技巧

数组转字符串

FRUITS=("apple" "banana" "orange")

# 用空格连接
STR="${FRUITS[*]}"
echo $STR    # apple banana orange

# 用指定分隔符连接
STR=$(IFS=','; echo "${FRUITS[*]}")
echo $STR    # apple,banana,orange

字符串转数组

STR="apple,banana,orange"

# 用 IFS 分割
IFS=',' read -ra FRUITS <<< "$STR"
echo ${FRUITS[@]}    # apple banana orange

数组去重

FRUITS=("apple" "banana" "apple" "orange" "banana")

# 用关联数组去重
declare -A SEEN
UNIQUE=()
for FRUIT in "${FRUITS[@]}"; do
    if [[ -z ${SEEN[$FRUIT]} ]]; then
        UNIQUE+=("$FRUIT")
        SEEN[$FRUIT]=1
    fi
done
echo ${UNIQUE[@]}    # apple banana orange

数组包含判断

FRUITS=("apple" "banana" "orange")

# 判断元素是否存在
contains() {
    local ELEMENT=$1
    shift
    local ARR=("$@")
    for ITEM in "${ARR[@]}"; do
        [[ $ITEM == $ELEMENT ]] && return 0
    done
    return 1
}

if contains "banana" "${FRUITS[@]}"; then
    echo "包含 banana"
fi

数组合并

A=("a" "b")
B=("c" "d")

# 合并
C=("${A[@]}" "${B[@]}")
echo ${C[@]}    # a b c d

小结

数组使用要点:

  1. 普通数组用 () 定义
  2. 关联数组需要 declare -A
  3. 访问用 ${ARR[index]}
  4. 长度用 ${#ARR[@]}
  5. 遍历推荐用 "${ARR[@]}"(加引号)