在Shell脚本编程中,数组是存储多个值的强大数据结构。与许多高级编程语言不同,Shell的数组实现有其独特的语法和特性。我刚开始接触Shell数组时,经常被它的索引方式和操作语法困扰,直到真正理解其设计哲学后才豁然开朗。
Shell数组主要分为两种类型:
重要提示:不同Shell解释器对数组的支持程度不同,本文以bash为例进行说明。如果需要在其他Shell环境中使用数组,请先确认其支持情况。
创建数组最简单的方式是直接赋值:
bash复制fruits=("Apple" "Banana" "Orange")
也可以显式声明数组类型:
bash复制declare -a indexed_array # 显式声明索引数组
declare -A hash_array # 显式声明关联数组
实际工作中,我总结出几种常用的数组初始化模式:
bash复制colors=("red" "green" "blue")
bash复制files[0]="document.txt"
files[1]="report.pdf"
bash复制processes=($(ps -ef | awk '{print $2}'))
bash复制declare -A user_info
user_info["name"]="John"
user_info["age"]=30
经验之谈:从命令输出初始化数组时,务必注意IFS(内部字段分隔符)的设置,不当的IFS会导致元素分割错误。安全做法是先备份原IFS,操作后再恢复:
bash复制OLD_IFS=$IFS
IFS=$'\n' # 按换行符分割
array=($(command))
IFS=$OLD_IFS
访问数组元素的基本语法:
bash复制echo ${fruits[0]} # 输出第一个元素
遍历数组的几种经典方法:
bash复制for ((i=0; i<${#fruits[@]}; i++)); do
echo "Element $i: ${fruits[$i]}"
done
bash复制for fruit in "${fruits[@]}"; do
echo "$fruit"
done
bash复制for key in "${!user_info[@]}"; do
echo "$key => ${user_info[$key]}"
done
数组切片是Shell脚本中的高频操作:
bash复制echo ${fruits[@]:1:2} # 从索引1开始取2个元素
数组合并的实用技巧:
bash复制combined=("${fruits[@]}" "${colors[@]}")
获取数组长度:
bash复制echo ${#fruits[@]} # 元素个数
检查元素是否存在(关联数组特别有用):
bash复制if [[ -v user_info["name"] ]]; then
echo "Key exists"
fi
下面分享一个我在实际工作中开发的日志分析脚本片段,展示了数组的强大应用:
bash复制#!/bin/bash
# 分析最近5个日志文件的错误类型
log_files=($(ls -t /var/log/app/*.log | head -5))
declare -A error_counts
for file in "${log_files[@]}"; do
errors=($(grep -oP 'ERROR_\w+' "$file"))
for err in "${errors[@]}"; do
if [[ -v error_counts["$err"] ]]; then
((error_counts["$err"]++))
else
error_counts["$err"]=1
fi
done
done
# 输出错误统计
echo "Error Statistics:"
for err in "${!error_counts[@]}"; do
printf "%-20s %d\n" "$err" "${error_counts[$err]}"
done | sort -nr -k2
这个脚本展示了如何:
在处理大型数组时,我总结出几个优化点:
bash复制declare -a big_array
big_array=( $(printf "%0.sx " $(seq 1 10000)) )
bash复制# 不佳的做法
for i in {1..100}; do
array+=("$i")
done
# 更好的做法
numbers=({1..100})
bash复制declare -A my_array # 必须声明
my_array["key"]="value"
bash复制arr=(a b c)
echo ${arr[3]} # 空输出,不会报错但可能导致逻辑错误
bash复制names=("John Doe" "Alice Smith")
for name in ${names[@]}; do # 错误!会拆分成4个词
for name in "${names[@]}"; do # 正确
bash复制declare -ra const_array=(1 2 3)
const_array[0]=4 # 报错:只读数组
为了提高代码复用性,我通常会封装一些数组处理函数:
bash复制# 检查元素是否在数组中
array_contains() {
local array="$1[@]"
local seeking=$2
local in=0
for element in "${!array}"; do
if [[ $element == "$seeking" ]]; then
in=1
break
fi
done
return $in
}
# 使用示例
fruits=("Apple" "Banana" "Orange")
if array_contains fruits "Apple"; then
echo "Found Apple"
fi
另一个实用函数是数组去重:
bash复制array_unique() {
declare -A tmp_array
declare -a unique_array
for i in "$@"; do
[[ $i ]] && tmp_array["$i"]=1
done
unique_array=("${!tmp_array[@]}")
echo "${unique_array[@]}"
}
# 使用示例
duplicates=(1 2 2 3 4 4 4 5)
unique=($(array_unique "${duplicates[@]}"))
echo "${unique[@]}" # 输出:1 2 3 4 5
虽然本文主要基于bash,但在实际工作中我们可能需要在不同Shell环境中使用数组。以下是一些兼容性处理技巧:
bash复制if [[ -n "$BASH_VERSION" && ${BASH_VERSINFO[0]} -ge 4 ]]; then
declare -A safe_array
else
echo "Warning: Associative arrays not supported"
fi
bash复制# 使用前缀+普通变量模拟
prefix="user_"
name_key="${prefix}name"
age_key="${prefix}age"
eval "$name_key='John'"
eval "$age_key=30"
# 访问
eval "echo \$$name_key"
bash复制# 使用位置参数作为数组
set -- "item1" "item2" "item3"
for item; do
echo "$item"
done
Shell数组在系统管理、数据处理等方面有广泛应用,以下是我遇到的一些典型场景:
bash复制declare -A config
while IFS='=' read -r key value; do
[[ $key && $value ]] && config["$key"]="$value"
done < config.properties
echo "Server: ${config["server"]}"
echo "Port: ${config["port"]}"
bash复制# 查找并处理所有.jpg文件
images=()
while IFS= read -r -d $'\0' file; do
images+=("$file")
done < <(find . -name "*.jpg" -print0)
# 并行处理
max_jobs=4
for img in "${images[@]}"; do
((i=i%max_jobs)); ((i++==0)) && wait
process_image "$img" &
done
bash复制# 统计访问日志中的国家分布
declare -A countries
log_lines=($(cat access.log))
for line in "${log_lines[@]}"; do
country=$(echo "$line" | awk '{print $NF}')
((countries[$country]++))
done
# 输出TOP 5国家
for country in "${!countries[@]}"; do
echo "${countries[$country]} $country"
done | sort -nr | head -5
调试数组相关脚本时,这些技巧非常有用:
bash复制declare -p fruits # 输出:declare -a fruits=([0]="Apple" [1]="Banana" [2]="Orange")
bash复制set -x
array_operation
set +x
bash复制declare -p fruits | jq -R 'split("=")[1] | fromjson'
bash复制echo "${!fruits[@]}" # 输出所有有效索引
经过多年Shell脚本开发,我总结了以下数组使用的最佳实践: