Skip to content

脚本优化

编写高效、健壮的 Shell 脚本需要从性能、可读性和可靠性多个维度进行优化。

性能优化

减少子进程创建

bash
# 慢:每次调用都 fork 子进程
for i in $(seq 1 1000); do
    echo $i
done

# 快:使用内置语法
for (( i=1; i<=1000; i++ )); do
    echo $i
done

# 慢:外部命令处理字符串
length=$(echo "$str" | wc -c)

# 快:使用参数扩展
length=${#str}

减少管道层数

bash
# 慢:多个管道
cat file.txt | grep "error" | grep -v "warning" | wc -l

# 快:合并到单个命令
grep -c "error" file.txt

# 或者使用 awk 一次完成
awk '/error/ && !/warning/' file.txt | wc -l

使用内置命令

bash
# 慢:外部命令
result=$(echo "$str" | tr 'a-z' 'A-Z')

# 快:Shell 内置(Bash 4+)
result="${str^^}"

# 慢
basename=$(echo "$path" | awk -F/ '{print $NF}')

# 快:参数扩展
basename="${path##*/}"

避免在循环中执行昂贵操作

bash
# 慢:每次循环都调用 date
for file in *.log; do
    timestamp=$(date +%Y%m%d)
    mv "$file" "${file%.log}_${timestamp}.log"
done

# 快:只调用一次
timestamp=$(date +%Y%m%d)
for file in *.log; do
    mv "$file" "${file%.log}_${timestamp}.log"
done

可靠性优化

错误处理三件套

bash
#!/bin/bash

set -e          # 遇到错误立即退出
set -u          # 使用未定义变量时报错
set -o pipefail # 管道中任何命令失败都视为失败

# 简写
set -euo pipefail

使用本地变量

bash
#!/bin/bash

# 不好:污染全局变量
process() {
    result="处理结果"
    temp_file="/tmp/temp$$"
}

# 好:使用 local
process() {
    local result="处理结果"
    local temp_file="/tmp/temp$$"
    echo "$result"
}

安全地处理文件名

bash
# 不好:文件名含空格会出错
for file in $(ls); do
    cat $file
done

# 好:使用 glob 和双引号
for file in *; do
    cat "$file"
done

# 处理特殊字符开头的文件名
for file in ./*; do
    cat "$file"
done

临时文件安全处理

bash
#!/bin/bash

# 使用 mktemp 创建安全的临时文件
temp_file=$(mktemp /tmp/myapp_XXXXXX.tmp)
temp_dir=$(mktemp -d /tmp/myapp_XXXXXX)

# 注册清理
trap 'rm -f "$temp_file"; rm -rf "$temp_dir"' EXIT INT TERM

echo "处理数据..." > "$temp_file"

可读性优化

有意义的变量名

bash
# 不好
t=$(date +%s)
for f in /var/log/*.log; do
    s=$(stat -c %s "$f")
    if (( s > 1000000 )); then
        echo "$f"
    fi
done

# 好
current_timestamp=$(date +%s)
max_log_size=1000000

for log_file in /var/log/*.log; do
    file_size=$(stat -c %s "$log_file")
    if (( file_size > max_log_size )); then
        echo "大文件:$log_file(${file_size} 字节)"
    fi
done

提取函数

bash
#!/bin/bash

# 不好:所有逻辑堆在一起
# ...200 行脚本...

# 好:拆分为函数
setup_environment() {
    export APP_HOME="/opt/myapp"
    export APP_LOG="$APP_HOME/logs"
    mkdir -p "$APP_LOG"
}

check_dependencies() {
    for cmd in curl jq awk; do
        command -v "$cmd" >/dev/null 2>&1 || {
            echo "缺少依赖:$cmd" >&2
            exit 1
        }
    done
}

main() {
    setup_environment
    check_dependencies
    # 主逻辑...
}

main "$@"

脚本头部规范

bash
#!/usr/bin/env bash
#
# 脚本名称:deploy.sh
# 功能描述:自动化部署脚本
# 作者:运维团队
# 创建日期:2025-01-01
# 使用方法:./deploy.sh [环境] [版本]
# 示例:./deploy.sh production v1.2.3
#

set -euo pipefail

# 常量定义
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly SCRIPT_NAME="$(basename "$0")"
readonly LOG_FILE="/var/log/${SCRIPT_NAME%.sh}.log"

# 日志函数
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"; }
error() { log "ERROR: $*" >&2; exit 1; }

# 参数验证
[[ $# -lt 2 ]] && error "用法:$SCRIPT_NAME <环境> <版本>"

调试优化

bash
#!/bin/bash

# 开启调试模式
set -x   # 打印每条执行的命令

# 只调试特定代码段
set -x
# 需要调试的代码...
set +x

# 使用环境变量控制调试
[[ "${DEBUG:-}" == "1" ]] && set -x

# 输出带行号的错误信息
err_line() {
    echo "错误发生在第 ${BASH_LINENO[0]} 行" >&2
}
trap err_line ERR

兼容性优化

bash
#!/usr/bin/env bash
# 使用 env 查找 bash,提高可移植性

# 检测 Shell 版本
if (( BASH_VERSINFO[0] < 4 )); then
    echo "需要 Bash 4.0 以上版本" >&2
    exit 1
fi

# 使用 POSIX 兼容语法(若需要在 sh 下运行)
# 避免使用:[[ ]]、(( ))、{a..z}、数组等 Bash 特性