Shell 脚本编程技巧

2026-06-22 · 6 阅读 · 547字
GitLinux
Shell 脚本编程技巧

Shell 脚本编程技巧

基础语法

Shebang 与环境

#!/bin/bash
# 推荐使用 /usr/bin/env 提高可移植性
#!/usr/bin/env bash

set -e   # 遇到错误立即退出
set -u   # 使用未定义变量时报错
set -o pipefail  # 管道中任意命令失败都返回非零
set -x   # 调试模式,打印执行的命令

变量

# 定义变量(等号两侧无空格)
name="World"
readonly API_URL="https://api.example.com"

# 使用变量
echo "Hello, ${name}!"

# 默认值
echo "${USER:-default_user}"
echo "${API_KEY:=default_key}"

# 字符串替换
str="hello world"
echo "${str/hello/hi}"     # hi world
echo "${str//o/O}"         # hellO wOrld

# 子串
echo "${str:0:5}"          # hello

数组

# 索引数组
fruits=("apple" "banana" "orange")
echo "${fruits[0]}"        # apple
echo "${fruits[@]}"        # 全部元素
echo "${#fruits[@]}"       # 数组长度

# 关联数组(Bash 4+)
declare -A user
user[name]="Alice"
user[role]="admin"
echo "${user[name]}"

流程控制

条件判断

# if 语句
if [[ -f "$file" ]]; then
    echo "文件存在"
elif [[ -d "$file" ]]; then
    echo "目录存在"
else
    echo "不存在"
fi

# 常用文件测试
[[ -f "$file" ]]    # 是否是文件
[[ -d "$dir" ]]     # 是否是目录
[[ -x "$bin" ]]     # 是否可执行
[[ -n "$str" ]]     # 字符串非空
[[ -z "$str" ]]     # 字符串为空
[[ "$a" == "$b" ]]  # 字符串相等
[[ "$a" -gt "$b" ]] # 数值大于(lt:小于, ge:大于等于, le:小于等于)

# case 语句
case "$1" in
    start)
        echo "Starting..."
        ;;
    stop|restart)
        echo "Stopping..."
        ;;
    *)
        echo "Usage: $0 {start|stop|restart}"
        exit 1
        ;;
esac

循环

# for 循环
for i in {1..5}; do
    echo "Number: $i"
done

# 遍历文件
for file in /var/log/*.log; do
    [[ -f "$file" ]] || continue
    echo "Processing: $file"
done

# while 循环
while IFS= read -r line; do
    echo "$line"
done < input.txt

# 计数循环
count=0
while [[ $count -lt 5 ]]; do
    echo $((count++))
done

函数

# 定义函数
log_info() {
    local msg="$1"
    echo "[INFO] $(date '+%Y-%m-%d %H:%M:%S') - $msg"
}

log_error() {
    local msg="$1"
    echo "[ERROR] $(date '+%Y-%m-%d %H:%M:%S') - $msg" >&2
}

# 函数返回值
is_file_exists() {
    [[ -f "$1" ]]
}

# 使用函数
log_info "开始部署"
if is_file_exists "config.yml"; then
    log_info "配置文件存在"
else
    log_error "配置文件缺失"
    exit 1
fi

错误处理

# 陷阱信号
cleanup() {
    echo "清理临时文件..."
    rm -rf /tmp/deploy_*
}
trap cleanup EXIT

# 忽略特定错误
set +e
some_command_that_might_fail
set -e

# 超时控制
timeout 30 curl -s https://example.com || {
    echo "请求超时"
    exit 1
}

# 重试逻辑
retry() {
    local n=0
    local max=3
    local delay=5
    until [[ $n -ge $max ]]; do
        "$@" && return
        n=$((n+1))
        echo "重试 $n/$max..."
        sleep $delay
    done
    return 1
}
retry curl -s https://api.example.com

最佳实践

参数解析

#!/bin/bash
usage() {
    echo "Usage: $0 [-h] [-v] [-o output] input"
    exit 1
}

verbose=false
output=""

while getopts "hvo:" opt; do
    case $opt in
        h) usage ;;
        v) verbose=true ;;
        o) output="$OPTARG" ;;
        *) usage ;;
    esac
done

shift $((OPTIND - 1))
[[ $# -lt 1 ]] && usage

安全检查

# 检查必要命令
for cmd in curl jq docker; do
    if ! command -v "$cmd" &>/dev/null; then
        echo "错误:未找到命令 $cmd"
        exit 1
    fi
done

# 检查 root 权限
if [[ $EUID -ne 0 ]]; then
    echo "请以 root 用户运行"
    exit 1
fi

输出处理

# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
NC='\033[0m'
echo -e "${GREEN}成功${NC}"
echo -e "${RED}失败${NC}"

# 进度显示
total=${#files[@]}
for i in "${!files[@]}"; do
    printf "\r处理中: %d/%d" $((i+1)) $total
    # ... 处理逻辑
done
echo

总结

编写 Shell 脚本时始终使用 set -euo pipefail 增强安全性,善用函数提高代码复用性,使用 trap 确保资源清理。关注可读性和错误处理,让脚本不光能跑,还要跑得稳。测试建议使用 ShellCheck 做静态分析。