Shell脚本编写Skill shell-scripting

Shell脚本编写技能,涵盖Bash/Zsh脚本开发、自动化任务处理、命令行工具创建、系统管理脚本编写、错误处理机制、文本处理和脚本调试。关键词:Shell脚本、Bash编程、自动化脚本、CLI工具、系统管理、脚本调试、Linux运维、命令行工具、脚本优化、Shell最佳实践。

DevOps 0 次安装 0 次浏览 更新于 2/28/2026

name: shell-scripting description: Shell脚本编写的最佳实践和模式。适用于编写bash/zsh脚本、自动化任务、创建CLI工具或调试shell命令。 author: Joseph OBrien status: unpublished updated: ‘2025-12-23’ version: 1.0.1 tag: skill type: skill

Shell脚本编写

全面的shell脚本编写技能,涵盖bash/zsh模式、自动化、错误处理和CLI工具开发。

何时使用此技能

  • 编写自动化脚本
  • 创建CLI工具
  • 系统管理任务
  • 构建和部署脚本
  • 日志处理和分析
  • 文件操作和批量操作
  • 定时任务和计划任务

脚本结构

模板

#!/usr/bin/env bash
# 脚本: name.sh
# 描述: 此脚本的功能
# 用法: ./name.sh [选项] <参数>

set -euo pipefail  # 错误时退出,未定义变量,管道失败
IFS=$'
\t'        # 更安全的单词分割

# 常量
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly SCRIPT_NAME="$(basename "${BASH_SOURCE[0]}")"

# 默认值
VERBOSE=false
DRY_RUN=false

# 函数
usage() {
    cat <<EOF
用法: $SCRIPT_NAME [选项] <参数>

选项:
    -h, --help      显示此帮助信息
    -v, --verbose   启用详细输出
    -n, --dry-run   显示将要执行的操作
EOF
}

log() {
    echo "[$(date +'%Y-%m-%d %H:%M:%S')] $*" >&2
}

error() {
    log "错误: $*"
    exit 1
}

# 主逻辑
main() {
    # 解析参数
    while [[ $# -gt 0 ]]; do
        case "$1" in
            -h|--help)
                usage
                exit 0
                ;;
            -v|--verbose)
                VERBOSE=true
                shift
                ;;
            -n|--dry-run)
                DRY_RUN=true
                shift
                ;;
            *)
                break
                ;;
        esac
    done

    # 你的逻辑在这里
}

main "$@"

错误处理

设置选项

set -e          # 任何错误时退出
set -u          # 未定义变量时报错
set -o pipefail # 管道失败即脚本失败
set -x          # 调试:打印每个命令(谨慎使用)

清理陷阱

cleanup() {
    rm -f "$TEMP_FILE"
    log "清理完成"
}
trap cleanup EXIT

# 同时处理特定信号
trap 'error "脚本被中断"' INT TERM

错误检查模式

# 检查命令是否存在
command -v jq >/dev/null 2>&1 || error "需要jq但未安装"

# 检查文件是否存在
[[ -f "$FILE" ]] || error "文件未找到: $FILE"

# 检查目录是否存在
[[ -d "$DIR" ]] || mkdir -p "$DIR"

# 检查变量是否设置
[[ -n "${VAR:-}" ]] || error "VAR未设置"

# 显式检查退出状态
if ! some_command; then
    error "some_command失败"
fi

变量与替换

变量扩展

# 默认值
${VAR:-default}     # 如果VAR未设置或为空则使用默认值
${VAR:=default}     # 如果VAR未设置或为空则设置为默认值
${VAR:+value}       # 如果VAR已设置则使用value
${VAR:?error msg}   # 如果VAR未设置或为空则报错

# 字符串操作
${VAR#pattern}      # 移除最短前缀匹配
${VAR##pattern}     # 移除最长前缀匹配
${VAR%pattern}      # 移除最短后缀匹配
${VAR%%pattern}     # 移除最长后缀匹配
${VAR/old/new}      # 替换第一个匹配项
${VAR//old/new}     # 替换所有匹配项
${#VAR}             # VAR的长度

数组

# 声明数组
declare -a ARRAY=("一" "二" "三")

# 访问元素
echo "${ARRAY[0]}"     # 第一个元素
echo "${ARRAY[@]}"     # 所有元素
echo "${#ARRAY[@]}"    # 元素数量
echo "${!ARRAY[@]}"    # 所有索引

# 遍历
for item in "${ARRAY[@]}"; do
    echo "$item"
done

# 追加
ARRAY+=("四")

关联数组

declare -A MAP
MAP["键1"]="值1"
MAP["键2"]="值2"

# 访问
echo "${MAP[键1]}"

# 检查键是否存在
[[ -v MAP[键1] ]] && echo "键1存在"

# 遍历
for key in "${!MAP[@]}"; do
    echo "$key: ${MAP[$key]}"
done

控制流

条件语句

# 字符串比较
[[ "$str" == "value" ]]
[[ "$str" != "value" ]]
[[ -z "$str" ]]  # 为空
[[ -n "$str" ]]  # 不为空

# 数字比较
[[ "$num" -eq 5 ]]  # 等于
[[ "$num" -ne 5 ]]  # 不等于
[[ "$num" -lt 5 ]]  # 小于
[[ "$num" -gt 5 ]]  # 大于

# 文件测试
[[ -f "$file" ]]  # 文件存在
[[ -d "$dir" ]]   # 目录存在
[[ -r "$file" ]]  # 可读
[[ -w "$file" ]]  # 可写
[[ -x "$file" ]]  # 可执行

# 逻辑运算符
[[ "$a" && "$b" ]]  # 与
[[ "$a" || "$b" ]]  # 或
[[ ! "$a" ]]        # 非

循环

# For循环
for i in {1..10}; do
    echo "$i"
done

# While循环
while read -r line; do
    echo "$line"
done < "$file"

# 进程替换
while read -r line; do
    echo "$line"
done < <(command)

# C风格for循环
for ((i=0; i<10; i++)); do
    echo "$i"
done

输入/输出

读取输入

# 从用户读取
read -r -p "输入名称: " name

# 读取密码(隐藏)
read -r -s -p "密码: " password

# 带超时读取
read -r -t 5 -p "快! " answer

# 逐行读取文件
while IFS= read -r line; do
    echo "$line"
done < "$file"

输出与重定向

# 重定向标准输出
command > file      # 覆盖
command >> file     # 追加

# 重定向标准错误
command 2> file

# 重定向两者
command &> file
command > file 2>&1

# 丢弃输出
command > /dev/null 2>&1

# Tee(输出并保存)
command | tee file

文本处理

常见模式

# 查找并处理文件
find . -name "*.log" -exec grep "ERROR" {} +

# 处理CSV
while IFS=, read -r col1 col2 col3; do
    echo "$col1: $col2"
done < file.csv

# JSON处理(使用jq)
jq '.key' file.json
jq -r '.items[]' file.json

# AWK单行命令
awk '{print $1}' file           # 第一列
awk -F: '{print $1}' /etc/passwd  # 自定义分隔符
awk 'NR > 1' file               # 跳过表头

# SED单行命令
sed 's/old/new/g' file          # 全部替换
sed -i 's/old/new/g' file       # 原地编辑
sed -n '10,20p' file            # 打印第10-20行

最佳实践

应该做

  • 引用所有变量扩展:"$VAR"
  • 使用[[ ]]而非[ ]进行测试
  • 使用$(command)而非反引号
  • 检查返回值
  • 对常量使用readonly
  • 在函数中使用local
  • 提供--help选项
  • 使用有意义的退出码

不应该做

  • 解析ls输出
  • 对不受信任的输入使用eval
  • 假设路径中没有空格
  • 忽略shellcheck警告
  • 编写一个巨型脚本(模块化)

参考文件

  • references/one_liners.md - 有用的单行命令

与其他技能的集成

  • developer-experience - 用于工具自动化
  • debugging - 用于脚本调试
  • testing - 用于脚本测试模式