mcpField
B
bash

bash

github.com/bentsolheim开发1040 次安装9 周前更新
代码仓库

关于 bash

bash 是一项指导 AI Agent 编写、编辑、审查和重构 Shell 脚本的技能。它区分简单脚本与复杂脚本,并提供结构、错误处理和测试方面的规范。

主要功能

  1. 01区分简单与常规脚本
  2. 02脚本结构模板
  3. 03参数解析与依赖检查
  4. 04显式错误处理与退出码
  5. 05测试与 source 检查

适用场景

  1. 01编写部署或备份脚本
  2. 02审查或重构 Bash 代码
  3. 03编写 CI 或数据转换小脚本

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/bentsolheim/public-skills --skill bash

安装后在 Claude Code 中输入/bash确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

CLAUDE.md
README.md
scripts/
scaffold.sh
templates/
script-template.sh
simple-script-template.sh

详细介绍

Bash 最佳实践技能

此技能可确保所有 Bash 脚本遵循企业级最佳实践,提升可维护性、可靠性和易用性。

适用场景

在以下情况中,应自动调用此技能:

  • 创建新的 Bash 脚本或 Shell 脚本
  • 编辑现有的 .sh 或 .bash 文件
  • 用户请求用于自动化、部署、备份或系统任务的脚本
  • 审查或重构 Bash 代码
  • 用户提及:Bash、Shell 脚本、main 函数、参数解析、脚本结构

脚本复杂度指南

此技能将脚本分为两类,并为其规定不同的结构要求:

简单脚本

以简单直接的方式做好一件事,不添加不必要的形式。

特点:

  • 实际逻辑少于约 30 行(不包括注释和空白)
  • 不需要命令行参数
  • 目的单一、明确
  • 流程线性或仅有少量分支
  • 输出通常由其他程序或脚本使用

**示例:**版本检测、环境检查、数据格式转换、简单计算

结构要求:

  • Main 函数和守卫子句:可选
  • Usage 函数:不需要(无需记录参数)
  • 参数解析:不适用
  • 彩色输出:可选(通常不需要)
  • 可以直接执行,无需用函数封装
普通脚本

范围较广,需要完整结构以便维护。

特点:

  • 逻辑超过约 30 行,或
  • 接受命令行参数,或
  • 包含多个函数或复杂分支,或
  • 供人交互使用,或
  • 需要详细文档

**示例:**部署工具、备份工具、系统维护脚本、开发工具

结构要求:

  • Main 函数和守卫子句:必需
  • Usage 函数:必需
  • 参数解析:必需(如果接受参数)
  • 完整的错误处理:必需
  • 用户友好的输出:必需(适用于交互式脚本)

应用的核心原则

普通脚本

编写普通脚本时,请确保:

  1. Main 函数模式和守卫子句——每个脚本都必须有一个 main() 函数
  2. Usage 函数——提供清晰的帮助文档
  3. 参数解析——在 main 中以结构化方式处理选项
  4. 依赖项检查——明确验证所需工具
  5. 明确的错误处理——绝不使用 set -e,应显式处理错误
  6. 函数组织——顺序恰当,职责单一
  7. 用户友好的输出——提供带状态指示的彩色结构化输出
简单脚本

编写简单脚本时,请确保:

  1. 清晰的用途注释——在脚本顶部说明脚本的功能
  2. 明确的错误处理——绝不使用 set -e,应显式处理错误
  3. 正确的退出码——成功时为 0,失败时为非零值
  4. 将错误输出到 stderr——使用 >&2 输出错误消息
  5. 变量安全——给变量加引号,并处理未定义的情况

脚本结构模板

普通脚本模板

每个新 Bash 脚本都应遵循此结构:

#!/usr/bin/env bash

# Global declarations
DEPENDENCIES=(jq curl git)  # List external tools needed
SCRIPT_NAME=$(basename "$0")
VERSION="1.0.0"

# Color definitions (if outputting to terminal)
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color

# Allow users to disable colors
if [[ -n "${NO_COLOR:-}" ]] || [[ "${TERM:-}" == "dumb" ]]; then
    RED=""
    GREEN=""
    YELLOW=""
    BLUE=""
    NC=""
fi

function usage() {
    cat <<EOM

Brief description of what this script does.

usage: ${SCRIPT_NAME} [options]

options:
    -i|--input        <file>     Input file to process (required)
    -o|--output       <file>     Output file (optional, defaults to stdout)
    -v|--verbose                 Enable verbose output
    -h|--help                    Show this help message
    --version                    Show version information

dependencies: ${DEPENDENCIES[@]}

examples:
    ${SCRIPT_NAME} -i data.txt -o report.json
    ${SCRIPT_NAME} --input data.txt --verbose

EOM
    exit 1
}

function main() {
    # Default values
    local input_file=""
    local output_file=""
    local verbose=false

    # Parse arguments
    while [ "$1" != "" ]; do
        case $1 in
        -i | --input)
            shift
            input_file="$1"
            ;;
        -o | --output)
            shift
            output_file="$1"
            ;;
        -v | --verbose)
            verbose=true
            ;;
        --version)
            echo "${SCRIPT_NAME} version ${VERSION}"
            exit 0
            ;;
        -h | --help)
            usage
            ;;
        *)
            echo "Error: Unknown option '$1'"
            usage
            ;;
        esac
        shift
    done

    # Validate required arguments
    if [ -z "$input_file" ]; then
        echo "Error: Input file is required" >&2
        usage
    fi

    # Check dependencies
    exit_on_missing_tools "${DEPENDENCIES[@]}"

    # Main script logic
    process_file "$input_file" "$output_file" "$verbose"
}

# Business logic functions
function process_file() {
    local input="$1"
    local output="$2"
    local verbose="$3"

    # Implementation here
    echo "Processing ${input}..."
}

# Utility functions
function exit_on_missing_tools() {
    for cmd in "$@"; do
        if command -v "$cmd" &>/dev/null; then
            continue
        fi
        printf "Error: Required tool '%s' is not installed or not in PATH\n" "$cmd"
        exit 1
    done
}

# Guard clause - only execute main if script is run directly
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
    main "$@"
    exit 0
fi
简单脚本模板

对于只做一件简单事情且不带参数的脚本:

#!/usr/bin/env bash
# Purpose: Determine version for CI builds based on git state
# Usage: determine-version.sh
# Output: Version string to stdout

# For simple scripts, direct execution is fine - no main() required

# Check if we're on a tag
TAG=$(git describe --exact-match --tags HEAD 2>/dev/null)

if [ -n "$TAG" ]; then
    # On a tag - use the tag as version
    echo "${TAG#v}"
else
    # Not on tag - determine from branch
    if [ -n "$GITHUB_HEAD_REF" ]; then
        # Pull request
        BRANCH="$GITHUB_HEAD_REF"
    elif [ -n "$GITHUB_REF_NAME" ]; then
        # Push to branch
        BRANCH="$GITHUB_REF_NAME"
    else
        # Fallback to current branch
        BRANCH=$(git symbolic-ref -q --short HEAD 2>/dev/null)
        if [ -z "$BRANCH" ]; then
            echo "Error: Unable to determine branch" >&2
            exit 1
        fi
    fi

    # Sanitize branch name for version string
    SANITIZED=$(echo "$BRANCH" | sed 's/[^a-zA-Z0-9._-]/_/g')
    echo "${SANITIZED}-SNAPSHOT"
fi

简单脚本的要点:

  • 在顶部添加清晰的用途注释
  • 直接执行,无需用 main() 封装
  • 错误仍应通过 >&2 输出到 stderr
  • 使用有意义的退出码(成功时为 0,失败时为非零值)
  • 仍要正确地给变量加引号
  • 没有参数时,无需 usage() 函数
  • 输出为数据时,无需彩色输出

详细最佳实践

1. 脚本引导
Main 函数模式
  • 始终使用 main 函数作为入口点
  • 这样其他脚本就能安全地加载此脚本
  • 结构清晰,便于测试
守卫子句
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
    main "$@"
    exit 0
fi
  • 必须位于脚本末尾
  • 允许在不执行脚本的情况下将其加载
  • 在 main 后始终显式添加 exit 0
2. 用法函数与参数解析
用法函数要求
  • 必须在 main() 函数之前定义
  • 使用 heredoc 以便清晰排版
  • 包含:描述、选项、依赖项、示例
  • 以状态码 1 退出
参数解析最佳实践
function main() {
    # Set defaults first
    local input_file=""
    local verbose=false

    # Parse with while loop and case
    while [ "$1" != "" ]; do
        case $1 in
        -i | --input)
            shift
            input_file="$1"
            ;;
        -v | --verbose)
            verbose=true
            ;;
        -h | --help)
            usage
            ;;
        *)
            echo "Error: Unknown option '$1'"
            usage
            ;;
        esac
        shift
    done

    # Validate after parsing
    if [ -z "$input_file" ]; then
        echo "Error: Input file is required" >&2
        usage
    fi
}
3. 依赖项声明与检查
始终声明依赖项
DEPENDENCIES=(jq curl git docker)
使用前检查
function exit_on_missing_tools() {
    for cmd in "$@"; do
        if command -v "$cmd" &>/dev/null; then
            continue
        fi
        printf "Error: Required tool '%s' is not installed or not in PATH\n" "$cmd"
        exit 1
    done
}
在用法说明中包含依赖项

在用法函数中列出依赖项,让用户提前了解所需条件。

4. 函数组织
正确的函数顺序(强制)
  1. usage() - 全局声明之后的第一个函数
  2. main() - 紧接 usage 之后
  3. 业务逻辑函数 - 核心功能
  4. 工具函数 - 通用辅助函数

绝不要添加类似“# 工具函数”的分节注释标题。代码结构本身就能说明问题。

函数设计原则
  • 单一职责:每个函数只做一件事
  • 输入/输出清晰:使用局部变量,返回有意义的状态
  • 名称明确:使用动词-名词组合(create_backup、validate_input)
  • 保持精简:将超过 50 行的函数拆分
  • 使用局部变量:始终使用 local 声明
5. 错误处理
避免使用 set -e

绝不要使用 set -e。应改用显式错误处理:

# Good - Explicit handling
if ! command; then
    echo "Error: Command failed" >&2
    exit 1
fi

# Good - Chain with ||
cd /some/directory || {
    echo "Error: Cannot change directory" >&2
    exit 1
}

# Good - Capture and check
output=$(command 2>&1)
if [ $? -ne 0 ]; then
    echo "Error: Command failed: $output" >&2
    exit 1
fi
错误处理规则
  • 始终使用 >&2 将错误重定向到 stderr
  • 说明失败原因及相关上下文
  • 使用有意义的退出码
  • 失败时清理临时文件
6. 用户友好的输出
彩色输出(适用时)
# Define colors at script top
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'

# Respect NO_COLOR environment
if [[ -n "${NO_COLOR:-}" ]] || [[ "${TERM:-}" == "dumb" ]]; then
    RED="" GREEN="" YELLOW="" BLUE="" NC=""
fi
输出函数
function print_header() {
    echo -e "${BLUE}=======================${NC}"
    echo -e "${BLUE}$1${NC}"
    echo -e "${BLUE}=======================${NC}"
}

function print_success() {
    echo -e "${GREEN}✅ $1${NC}"
}

function print_error() {
    echo -e "${RED}❌ Error: $1${NC}" >&2
}

function print_warning() {
    echo -e "${YELLOW}⚠️  Warning: $1${NC}"
}

function print_step() {
    local current="$1"
    local total="$2"
    local message="$3"
    echo -e "${YELLOW}[${current}/${total}] ${message}${NC}"
}
7. 保持 main 函数精简

main 只应执行以下操作:

  1. 解析参数
  2. 验证输入
  3. 检查依赖项
  4. 调用业务函数
  5. 处理高层级错误

业务逻辑应放在独立函数中。

8. 代码注释
避免过多注释
  • 命名清晰的函数本身就能说明用途
  • 只注释“为什么”,不要注释“做什么”
  • 在以下情况下使用注释:
    • 复杂算法
    • 不明显的变通方案
    • 业务逻辑规则
    • 外部 API 的特殊行为
# Bad - Obvious comment
# Increment counter
counter=$((counter + 1))

# Good - Explains why
# Exponential backoff with jitter to prevent thundering herd
delay=$((2 ** attempt * 1000 + RANDOM % 1000))

脚本生成检查清单

生成新脚本时,请确认:

  • Shebang 行:#!/usr/bin/env bash
  • 已声明 DEPENDENCIES 数组
  • 已首先定义 usage() 函数
  • main() 函数负责处理参数解析
  • 使用 exit_on_missing_tools 检查依赖项
  • 脚本末尾包含守卫子句
  • 使用显式错误处理(不使用 set -e)
  • 函数顺序正确
  • 函数中使用局部变量
  • 错误重定向到 stderr
  • 使用有意义的退出码
  • 支持 NO_COLOR 的彩色输出(如适用)

常见模式

交互式提示
# Yes/no confirmation
read -p "Continue? (y/n): " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
    echo "Operation cancelled"
    exit 0
fi

# Menu selection
echo "Select an option:"
echo "1) Full backup"
echo "2) Incremental backup"
read -p "Enter choice (1-2): " choice

# Password (hidden)
read -s -p "Enter password: " password
echo  # Add newline
临时文件
# Create temp file safely
temp_file=$(mktemp)
trap "rm -f '$temp_file'" EXIT

# Use temp directory
temp_dir=$(mktemp -d)
trap "rm -rf '$temp_dir'" EXIT
管道错误处理
# Without set -e, check PIPESTATUS
command1 | command2 | command3
if [ "${PIPESTATUS[0]}" -ne 0 ]; then
    echo "Error: command1 failed" >&2
    exit 1
fi

可参考的示例

用户要求编写常见任务脚本时,请采用以下模式:

备份脚本
  • 在备份名称中包含时间戳
  • 备份前确认源存在
  • 检查可用磁盘空间
  • 提供进度反馈
  • 根据保留策略清理旧备份
部署脚本
  • 先验证环境
  • 清晰展示各步骤的进展
  • 失败时回滚
  • 记录所有操作
  • 最后提供摘要
数据处理脚本
  • 验证输入格式
  • 高效处理大文件
  • 提供进度指示
  • 支持失败后恢复执行
  • 生成详细报告

如何选择简单脚本与普通脚本

决策树

回答以下问题,确定应使用哪种模式:

  1. 脚本是否接受命令行参数?

    • 是 → 使用普通脚本模式(需要 usage() 和参数解析)
    • 否 → 继续回答问题 2
  2. 逻辑是否超过约 30 行(不包括注释)?

    • 是 → 使用普通脚本模式(需要结构)
    • 否 → 继续回答问题 3
  3. 是否包含多个函数或复杂的分支逻辑?

    • 是 → 使用普通脚本模式(需要组织结构)
    • 否 → 继续回答问题 4
  4. 是否会由需要帮助的人员交互式运行?

    • 是 → 使用普通脚本模式(需要用户友好功能)
    • 否 → 继续回答问题 5
  5. 是否只执行一项简单且不言自明的任务?

    • 是 → 使用简单脚本模式
    • 否 → 使用普通脚本模式

不确定时,使用普通模式。 有用不上的结构,总比需要结构时却没有好。

简单脚本示例
  • determine-version.sh - 根据 git 状态输出版本字符串
  • check-env.sh - 验证环境变量是否已设置
  • format-json.sh - 将来自 stdin 的 JSON 重新格式化并输出到 stdout
  • count-lines.sh - 统计指定文件类型中的行数
  • is-production.sh - 如果当前位于生产分支,则返回 0
普通脚本示例
  • deploy.sh - 包含回滚操作的多步骤部署
  • backup.sh - 带保留策略管理的数据库备份
  • setup-dev.sh - 开发环境初始化
  • test-runner.sh - 可配置的测试执行
  • migrate-data.sh - 带验证的数据迁移

测试脚本

始终测试生成的脚本:

  1. 语法:bash -n script.sh
  2. 未定义变量:临时使用 set -u 运行
  3. 依赖项:在未安装所需工具的情况下运行
  4. 错误路径:使用无效输入进行测试
  5. 加载脚本:验证保护子句是否有效(适用于普通脚本)

请记住

生成或修改的每个脚本都应达到可用于生产环境的标准:

  • 便于他人维护
  • 通过良好的结构实现自描述
  • 能妥善处理错误
  • 提供有用的输出
  • 遵循一致的模式

这些实践可确保脚本专业、可靠且易于使用。

评论

支持 Markdown