在日常的系统运维和自动化任务中,我们编写的脚本有时会处理大量数据或执行复杂逻辑。当脚本运行缓慢时,不仅影响效率,还可能因资源占用过高而干扰其他服务。经过多次实战排查,我总结了一些能显著提升脚本性能的具体技巧。

避免不必要的子进程创建

每启动一个外部命令,Shell都会创建一个新的子进程,这个过程涉及资源分配和环境切换,在循环中尤其消耗性能。

问题场景

# 低效写法
for file in *.log; do
    count=$(wc -l < "$file")
    echo "$file: $count lines"
done

优化方案

# 高效写法 - 使用内置命令替代
for file in *.log; do
    lines=0
    while IFS= read -r _; do
        ((lines++))
    done < "$file"
    echo "$file: $lines lines"
done

对于简单的文本处理,可以优先考虑Shell内置的字符串操作:

# 使用参数扩展替代grep和cut
filename="/path/to/file.txt"
basename=${filename##*/}  # 替代basename命令
dirname=${filename%/*}     # 替代dirname命令

优化循环结构

循环是性能瓶颈的常见来源,特别是在处理大文件或大量数据时。

文件读取优化

处理大文件时,避免多次读取同一文件:

# 低效 - 文件被读取两次
if grep -q "error" "$logfile"; then
    error_count=$(grep -c "error" "$logfile")
fi

# 高效 - 一次读取完成所有处理
if contents=$(grep "error" "$logfile"); then
    error_count=$(echo "$contents" | wc -l)
    # 进一步处理$contents
fi

使用更快的循环构造

# while循环通常比for循环更快
while IFS= read -r line; do
    # 处理每行数据
done < "input_file.txt"

# 对于数组遍历,for循环更合适
files=(*.txt)
for file in "${files[@]}"; do
    # 处理文件
done

合理使用工具组合

减少管道数量

每个管道都会创建新进程,尽量减少管道连接:

# 低效 - 多个管道
cat file.txt | grep "pattern" | sort | uniq

# 高效 - 合并命令
< file.txt grep "pattern" | sort -u

# 或者使用单个工具完成
awk '/pattern/ {print}' file.txt | sort -u

选择性能更好的工具

  • 对于简单文本搜索,grep 通常比 awksed 更快
  • 复杂文本处理时,awk 的单次处理能力优于多个命令组合
  • 大数据集排序时,确保有足够内存或使用 sort--buffer-size 选项

内存与I/O优化

变量使用策略

# 避免在循环中重复定义变量
readonly config_file="/etc/app.conf"
readonly temp_dir="/tmp/process"

# 使用数组代替多个变量
declare -a results
results+="$item1"
results+="$item2"

文件操作优化

# 批量文件操作优于单个处理
# 低效
for file in *.txt; do
    cp "$file" /backup/
done

# 高效
cp *.txt /backup/ 2>/dev/null || true

# 使用rsync进行大量文件同步
rsync -av --delete /source/ /destination/

实用性能检测技巧

时间测量

# 简单时间测量
start_time=$(date +%s.%N)
# 执行脚本主体
end_time=$(date +%s.%N)
runtime=$(echo "$end_time - $start_time" | bc)
echo "脚本运行时间: ${runtime}秒"

# 使用time命令获取详细统计
time ./your_script.sh

性能热点定位

通过设置 set -x 或在关键位置添加时间戳来识别瓶颈:

debug_time() {
    echo "[$(date +%T)] $1" >&2
}

debug_time "开始处理"
# 关键操作
debug_time "处理完成"

高级优化技巧

使用协程处理

对于I/O密集型任务,可以使用协程并行处理:

process_file() {
    local file=$1
    # 处理文件
    echo "处理完成: $file"
}

# 并行处理多个文件
for file in *.log; do
    process_file "$file" &
done

# 等待所有后台任务完成
wait

缓存机制

对于重复计算,引入简单缓存:

declare -A cache

get_file_size() {
    local file=$1
    if [[ -z "${cache[$file]}" ]]; then
        cache[$file]=$(stat -c%s "$file" 2>/dev/null || echo "0")
    fi
    echo "${cache[$file]}"
}

这些优化技巧源于实际工作中的性能问题排查,经过实践验证能够显著提升脚本执行效率。关键在于理解每个操作背后的代价,在功能需求和性能之间找到平衡点。