别再写丑陋的Shell脚本了,dyad如何让Unix命令行工具开发优雅10倍
在Unix/Linux的开发日常中,Shell脚本几乎无处不在。无论是自动化部署、数据处理,还是快速原型开发,Shell都是运维工程师和后端开发者手中最锋利的瑞士军刀。然而,当你真正深入编写复杂脚本时,会发现传统的Bash/Zsh脚本存在大量痛点:参数解析繁琐、错误处理混乱、代码可读性差、调试困难……
今天要介绍的这个开源项目 dyad-sh/dyad,正是为解决这些痛点而生。它是一个现代化的Shell脚本框架,旨在将现代软件工程的最佳实践带入传统的Shell编程世界,让命令行工具的开发变得既优雅又可靠。
如果你受够了写维护成本高、可读性差的Shell脚本,这篇文章将带你从零开始掌握dyad,学会如何用它构建专业级的命令行工具。
为什么dyad值得关注
传统Shell脚本的困境
在深入了解dyad之前,让我们先回顾一下传统Shell脚本开发中常见的几个典型场景:
场景一:参数解析的噩梦
#!/bin/bash
# 传统方式处理命令行参数
if [ "$1" == "-h" ] || [ "$1" == "--help" ]; then
echo "Usage: $0 [-n name] [-v] [--config FILE]"
exit 0
fi
NAME=""
VERBOSE=0
CONFIG="default.conf"
while [ $# -gt 0 ]; do
case "$1" in
-n|--name)
NAME="$2"
shift 2
;;
-v|--verbose)
VERBOSE=1
shift
;;
--config)
CONFIG="$2"
shift 2
;;
*)
echo "Unknown option: $1"
exit 1
;;
esac
done
这段代码看起来已经够复杂了,但实际项目中参数往往更多,类型检查、默认值、参数校验等需求会让代码膨胀到难以维护。
场景二:错误处理的迷茫
#!/bin/bash
# 传统的错误处理方式
some_command
if [ $? -ne 0 ]; then
echo "Command failed"
exit 1
fi
another_command || {
echo "Another command failed"
exit 1
}
错误处理散落在各处,状态码管理混乱,难以形成统一的错误报告机制。
场景三:代码组织的混乱
当脚本超过几百行时,没有模块化设计的Shell脚本会变成一团乱麻。函数定义散布各处,全局变量到处可见,修改一个功能可能影响完全不相关的逻辑。
dyad的核心价值
dyad针对上述痛点提供了系统性的解决方案:
声明式的参数定义:告别手动解析,使用简洁的DSL定义参数,dyad自动处理类型转换、默认值、必填检查。
结构化的输出系统:内置日志、进度条、表格输出等功能,让脚本输出既美观又专业。
健壮的错误处理:统一的错误收集与报告机制,支持错误堆栈追踪。
模块化的架构设计:支持脚本拆分、依赖管理、代码复用。
跨Shell兼容性:同时支持Bash和Zsh,部分功能兼容Dash。
更重要的是,dyad采用零外部依赖的设计,所有功能都是纯Shell实现,这意味着你可以轻松地将它集成到任何Unix/Linux环境中,无需担心依赖问题。
环境搭建
前置要求
在开始使用dyad之前,确保你的系统满足以下要求:
支持的Shell环境
- Bash 4.0 或更高版本
- Zsh 5.0 或更高版本
- 部分功能支持Dash(基础工具函数)
基础工具
- POSIX标准工具(awk、sed、grep等)
- Git(用于克隆仓库)
安装方式
dyad提供了多种安装方式,你可以根据实际需求选择最适合的方法。
方式一:单文件引入(推荐入门)
这是最简单的方式,直接下载dyad的核心文件到你的项目中:
# 创建项目目录
mkdir my-project && cd my-project
# 下载dyad核心文件
curl -fsSL https://raw.githubusercontent.com/dyad-sh/dyad/main/dyad.sh -o dyad.sh
# 在脚本中引入
cat > my_script.sh << 'EOF'
#!/usr/bin/env bash
# 引入dyad框架
source "$(dirname "$0")/dyad.sh"
# 你的代码
dyad_log info "Hello from dyad!"
EOF
chmod +x my_script.sh
./my_script.sh
方式二:Git子模块(推荐项目管理)
如果你使用Git管理项目,可以通过子模块的方式集成dyad:
# 初始化Git仓库(如果没有)
git init my-project && cd my-project
# 添加dyad作为子模块
git submodule add https://github.com/dyad-sh/dyad.git lib/dyad
# 创建项目脚本
cat > deploy.sh << 'EOF'
#!/usr/bin/env bash
set -euo pipefail
# 设置脚本目录为项目根目录
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
# 引入dyad
source "$PROJECT_ROOT/lib/dyad/dyad.sh"
# 你的部署逻辑
dyad_log info "Starting deployment..."
EOF
chmod +x deploy.sh
初始化子模块时需要执行:
git submodule update --init --recursive
方式三:系统全局安装
如果你希望在系统级别使用dyad,可以将其安装到系统路径:
# 克隆仓库
git clone https://github.com/dyad-sh/dyad.git /tmp/dyad
# 安装到系统路径(需要sudo权限)
sudo cp -r /tmp/dyad /opt/dyad
sudo ln -s /opt/dyad/dyad.sh /usr/local/bin/dyad
# 验证安装
dyad --version
方式四:使用安装脚本
dyad官方提供了快速安装脚本:
# 默认安装到 ~/.local/share/dyad
curl -fsSL https://get.dyad.sh | bash
# 自定义安装路径
curl -fsSL https://get.dyad.sh | bash -s -- --prefix=/opt/dyad
验证安装
安装完成后,运行以下命令验证dyad是否正常工作:
#!/usr/bin/env bash
source dyad.sh # 或 /usr/local/bin/dyad
# 检查dyad版本
echo "dyad version: $(dyad_version)"
你应该能看到dyad的版本信息。如果出现错误,检查以下几点:
- Shell版本是否满足要求(bash –version)
- 文件权限是否正确(chmod +x)
- 路径是否设置正确
核心功能详解
dyad的功能可以分为几个主要模块,每个模块解决Shell脚本开发中的特定问题。深入理解这些模块,将帮助你构建出专业级的命令行工具。
模块一:日志系统
日志是任何脚本不可或缺的功能。dyad提供了一个功能完善的日志系统,支持多种日志级别和输出格式。
基本日志输出
#!/usr/bin/env bash
source dyad.sh
# 使用不同级别的日志
dyad_log debug "这是一条调试信息"
dyad_log info "这是一条普通信息"
dyad_log warn "这是一条警告信息"
dyad_log error "这是一条错误信息"
dyad_log success "任务执行成功"
dyad_log step "步骤1:初始化环境"
默认情况下,日志会输出到标准错误(stderr),并带有颜色标记。如果需要输出到文件,可以设置日志路径:
#!/usr/bin/env bash
source dyad.sh
# 设置日志文件
dyad_log_set_file /var/log/myapp.log
# 设置日志级别(只显示info及以上)
dyad_log_set_level info
dyad_log debug "这条消息不会显示"
dyad_log info "这条消息会显示"
日志格式控制
#!/usr/bin/env bash
source dyad.sh
# 自定义日志格式
# %t - 时间戳
# %l - 日志级别
# %m - 消息内容
# %n - 脚本名称
dyad_log_set_format "[%t] [%l] %n: %m"
# 禁用颜色输出(适用于日志文件)
dyad_log_set_color false
带进度条的日志
对于耗时较长的任务,可以使用带进度的日志:
#!/usr/bin/env bash
source dyad.sh
# 显示进度条
for i in {1..100}; do
dyad_progress $i 100 "Processing files..."
sleep 0.05
done
echo "" # 换行,结束进度条输出
dyad_log success "处理完成!"
模块二:参数解析
这是dyad最核心的功能之一。通过声明式的方式定义命令行参数,自动处理解析、类型转换、默认值和验证。
基础参数定义
#!/usr/bin/env bash
source dyad.sh
# 定义参数
dyad_param_add --name name -n "User" required "用户名" \
--type string --placeholder "请输入用户名"
dyad_param_add --age age -a "18" "年龄" \
--type integer --min 0 --max 150
dyad_param_add --config config -c "config.json" "配置文件路径" \
--type file --exists
dyad_param_add --verbose verbose -v false "详细输出" \
--type boolean
dyad_param_add --files files -f [] "输入文件列表" \
--type string --nargs +
# 解析命令行参数
dyad_parse "$@"
# 访问解析后的值
echo "用户名: $(dyad_param_get name)"
echo "年龄: $(dyad_param_get age)"
echo "详细输出: $(dyad_param_get verbose)"
echo "文件数量: $(dyad_param_get files | wc -w)"
支持的参数类型
dyad支持多种内置类型,每种类型都有内置的验证逻辑:
#!/usr/bin/env bash
source dyad.sh
# 字符串类型(默认)
dyad_param_add --title title -t "" "标题" --type string
# 整数类型
dyad_param_add --port port -p "8080" "端口号" --type integer
# 浮点数类型
dyad_param_add --threshold threshold -th "0.75" "阈值" --type float
# 布尔类型
dyad_param_add --force force -f false "强制执行" --type boolean
# 文件路径类型(可选验证)
dyad_param_add --input input -i "" "输入文件" --type file
dyad_param_add --output output -o "" "输出文件" --type file --writable
# 目录路径类型
dyad_param_add --dir dir -d "" "工作目录" --type directory --exists
# 枚举类型
dyad_param_add --env env -e "dev" "环境" --type enum --choices dev staging production
# URL类型
dyad_param_add --url url -u "" "服务地址" --type url
参数验证
dyad提供了丰富的验证选项:
#!/usr/bin/env bash
source dyad.sh
# 数值范围验证
dyad_param_add --count count -c "10" "数量" \
--type integer --min 1 --max 100
# 字符串长度验证
dyad_param_add --username username -u "" "用户名" \
--type string --min-length 3 --max-length 20
# 正则表达式验证
dyad_param_add --email email -e "" "邮箱" \
--type string --pattern "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
# 自定义验证函数
dyad_param_add --date date -d "" "日期" \
--type string --validate my_date_validator
my_date_validator() {
local value="$1"
if [[ "$value" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]]; then
return 0
fi
echo "日期格式错误,应为 YYYY-MM-DD"
return 1
}
自动帮助信息
定义参数后,dyad可以自动生成专业的帮助信息:
#!/usr/bin/env bash
source dyad.sh
dyad_param_add --name name -n "" required "用户名"
dyad_param_add --email email -e "" "邮箱地址" --type email
dyad_param_add --verbose verbose -v false "详细输出" --type boolean
# 生成帮助信息
dyad_usage
# 自定义帮助头部
dyad_usage_set_header "我的工具 v1.0.0"
dyad_usage_set_footer "更多信息请访问 https://example.com"
执行 --help 或 -h 时会自动显示:
用法: mytool [选项]
必填参数:
-n, --name <STRING> 用户名
可选参数:
-e, --email <EMAIL> 邮箱地址
-v, --verbose <BOOLEAN> 详细输出 (默认: false)
-h, --help 显示帮助信息
模块三:表格输出
在构建CLI工具时,美观的表格输出能极大提升用户体验:
#!/usr/bin/env bash
source dyad.sh
# 定义表格数据
declare -A headers=(
["name"]="姓名"
["age"]="年龄"
["city"]="城市"
)
declare -a data=(
"张三|28|北京"
"李四|35|上海"
"王五|42|广州"
)
# 创建并输出表格
dyad_table_create "users" headers
for row in "${data[@]}"; do
IFS='|' read -r name age city <<< "$row"
dyad_table_add_row "users" "$name" "$age" "$city"
done
dyad_table_render "users"
输出效果:
┌──────────┬──────┬────────┐
│ 姓名 │ 年龄 │ 城市 │
├──────────┼──────┼────────┤
│ 张三 │ 28 │ 北京 │
│ 李四 │ 35 │ 上海 │
│ 王五 │ 42 │ 广州 │
└──────────┴──────┴────────┘
表格样式定制
#!/usr/bin/env bash
source dyad.sh
# 设置表格样式
dyad_table_set_style users bordered # 可选: simple, bordered, compact, rounded
# 设置列对齐方式
dyad_table_set_align users name left
dyad_table_set_align users age center
dyad_table_set_align users city right
# 设置列宽度
dyad_table_set_width users name 20
dyad_table_set_width users city 15
# 显示/隐藏边框
dyad_table_set_border users true
模块四:文件操作工具
dyad提供了一系列便捷的文件操作函数:
#!/usr/bin/env bash
source dyad.sh
# 安全读取文件
dyad_file_read() {
local file="$1"
if [ -f "$file" ]; then
cat "$file"
else
dyad_log error "文件不存在: $file"
return 1
fi
}
# 安全写入文件(支持原子操作)
dyad_file_write() {
local file="$1"
local content="$2"
echo "$content" > "$file"
}
# 文件比较(内容对比)
if dyad_file_diff "file1.txt" "file2.txt"; then
dyad_log info "文件内容相同"
else
dyad_log warn "文件内容不同"
fi
# 临时文件管理
dyad_tmpfile_create # 创建临时文件
local tmpfile
tmpfile=$(dyad_tmpfile_create)
echo "临时文件: $tmpfile"
# 使用完后清理
dyad_tmpfile_cleanup "$tmpfile"
# 递归创建目录
dyad_mkdir_p "/path/to/deep/nested/directory"
模块五:进度与任务管理
对于长时间运行的任务,dyad提供了任务管理功能:
#!/usr/bin/env bash
source dyad.sh
# 创建任务
dyad_task_create "deploy" "部署应用到服务器"
# 设置任务步骤
dyad_task_add_step "deploy" "prepare" "准备环境"
dyad_task_add_step "deploy" "build" "构建项目"
dyad_task_add_step "deploy" "upload" "上传文件"
dyad_task_add_step "deploy" "restart" "重启服务"
# 开始执行任务
dyad_task_start "deploy"
dyad_step_start "prepare"
sleep 1
dyad_step_complete "prepare"
dyad_step_start "build"
sleep 2
dyad_step_complete "build"
dyad_step_start "upload"
sleep 1
dyad_step_complete "upload"
dyad_step_start "restart"
sleep 1
dyad_step_complete "restart"
# 标记任务完成
dyad_task_complete "deploy"
模块六:并发与锁机制
在多进程环境下,dyad提供了文件锁功能:
#!/usr/bin/env bash
source dyad.sh
# 尝试获取锁
local lockfile="/tmp/myapp.lock"
if dyad_lock_acquire "$lockfile"; then
dyad_log info "成功获取锁,开始执行任务"
# 执行需要独占的操作
do_exclusive_work()
# 释放锁
dyad_lock_release "$lockfile"
else
dyad_log warn "无法获取锁,另一个实例正在运行"
exit 1
fi
# 或者使用超时获取锁
if dyad_lock_acquire "$lockfile" --timeout 10; then
# 成功获取
else
# 超时
fi
模块七:配置管理
dyad支持多种配置来源,优先级从高到低:
#!/usr/bin/env bash
source dyad.sh
# 加载配置文件(支持多种格式)
dyad_config_load "/etc/myapp.conf" # INI格式
dyad_config_load "./config.yaml" # YAML格式
dyad_config_load "$HOME/.myapprc" # 键值对格式
# 从环境变量加载
dyad_config_from_env "MYAPP_" "database"
# 访问配置值
local db_host
db_host=$(dyad_config_get "database.host" "localhost")
local db_port
db_port=$(dyad_config_get "database.port" "3306")
# 设置默认值
dyad_config_set "app.name" "My Application"
dyad_config_set "app.debug" false
实战教程:构建一个完整的CLI工具
现在让我们通过一个完整的实战项目,将上述功能串联起来。我们将构建一个名为 gitstat 的工具,用于分析Git仓库的统计数据。
项目初始化
首先创建项目结构:
mkdir -p gitstat/lib
cd gitstat
# 引入dyad作为子模块
git init
git submodule add https://github.com/dyad-sh/dyad.git lib/dyad
# 创建主脚本
cat > gitstat.sh << 'GITSTAT_EOF'
#!/usr/bin/env bash
# =============================================================================
# GitStat - Git仓库统计分析工具
# =============================================================================
set -euo pipefail
# 确保脚本可以从任意目录运行
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/lib/dyad/dyad.sh"
# =============================================================================
# 配置
# =============================================================================
readonly VERSION="1.0.0"
readonly APP_NAME="gitstat"
# =============================================================================
# 参数定义
# =============================================================================
# 定义命令行参数
dyad_param_add --repository repo -r "$(pwd)" \
"Git仓库路径" --type directory --exists
dyad_param_add --author author -a "" \
"过滤特定作者的提交" --type string
dyad_param_add --since since -s "1970-01-01" \
"统计起始日期" --type string
dyad_param_add --until until -u "$(date +%Y-%m-%d)" \
"统计结束日期" --type string
dyad_param_add --output output -o "" \
"输出格式 (text/json)" --type enum --choices text json --default text
dyad_param_add --verbose verbose -v false \
"显示详细输出" --type boolean
dyad_param_add --help help -h false \
"显示帮助信息" --type boolean
# =============================================================================
# 核心函数
# =============================================================================
# 显示工具头部信息
show_header() {
echo ""
dyad_log info "═══════════════════════════════════════════════════════════"
dyad_log info " GitStat v${VERSION} - Git仓库统计分析工具"
dyad_log info "═══════════════════════════════════════════════════════════"
echo ""
}
# 验证仓库
validate_repository() {
local repo_path="$1"
if [ ! -d "$repo_path/.git" ]; then
dyad_log error "目录不是Git仓库: $repo_path"
return 1
fi
dyad_log success "✓ 仓库验证通过: $repo_path"
}
# 获取提交统计
get_commit_stats() {
local repo_path="$1"
local author="$2"
local since="$3"
local until="$4"
dyad_log step "正在分析提交记录..."
cd "$repo_path"
# 构建git命令参数
local git_args=("log" "--since=$since" "--until=$until" "--pretty=format:%H|%an|%ae|%ai|%s")
if [ -n "$author" ]; then
git_args+=("--author=$author")
dyad_log info "作者过滤: $author"
fi
# 统计数据
local total_commits
total_commits=$(git "${git_args[@]}" 2>/dev/null | wc -l)
local total_additions
total_additions=$(git "${git_args[@]}" 2>/dev/null | \
xargs -I {} git show {} --stat --pretty=format: 2>/dev/null | \
grep -E "^\s+[0-9]+" | \
awk '{sum += $1} END {print sum}')
local total_deletions
total_deletions=$(git "${git_args[@]}" 2>/dev/null | \
xargs -I {} git show {} --stat --pretty=format: 2>/dev/null | \
grep -E "^\s+[0-9]+" | \
awk '{sum += $2} END {print sum}')
# 输出结果
echo "total_commits:$total_commits"
echo "total_additions:$total_additions"
echo "total_deletions:$total_deletions"
}
# 获取作者排名
get_author_leaderboard() {
local repo_path="$1"
local since="$2"
local until="$3"
dyad_log step "正在生成作者排名..."
cd "$repo_path"
local temp_file
temp_file=$(mktemp)
git log --since="$since" --until="$until" --pretty=format:"%an" 2>/dev/null | \
sort | uniq -c | sort -rn > "$temp_file"
# 定义表格
declare -A headers=(
["rank"]="排名"
["author"]="作者"
["commits"]="提交数"
["percentage"]="占比"
)
dyad_table_create "authors" headers
dyad_table_set_align "authors" rank center
dyad_table_set_align "authors" author left
dyad_table_set_align "authors" commits center
dyad_table_set_align "authors" percentage right
local total_commits
total_commits=$(git log --since="$since" --until="$until" --pretty=format:"%an" 2>/dev/null | wc -l)
local rank=1
while IFS= read -r line; do
local count
local author
count=$(echo "$line" | awk '{print $1}')
author=$(echo "$line" | awk '{print $2}')
local percentage
percentage=$(echo "scale=1; $count * 100 / $total_commits" | bc)
dyad_table_add_row "authors" "$rank" "$author" "$count" "${percentage}%"
rank=$((rank + 1))
done < "$temp_file"
rm -f "$temp_file"
dyad_log success "找到 $total_commits 条提交记录"
}
# 获取文件类型统计
get_file_type_stats() {
local repo_path="$1"
local since="$2"
local until="$3"
dyad_log step "正在统计文件类型..."
cd "$repo_path"
local temp_file
temp_file=$(mktemp)
git log --since="$since" --until="$until" --name-only --pretty=format:"" 2>/dev/null | \
grep -E "\.[a-zA-Z0-9]+$" | \
sed 's/.*\.//' | \
sort | \
uniq -c | \
sort -rn > "$temp_file"
# 定义表格
declare -A headers=(
["extension"]="扩展名"
["count"]="文件数"
["trend"]="趋势"
)
dyad_table_create "filetypes" headers
local top_extensions
top_extensions=$(head -10 "$temp_file")
while IFS= read -r line; do
local count
local ext
count=$(echo "$line" | awk '{print $1}')
ext=$(echo "$line" | awk '{print $2}')
# 根据数量判断趋势
local trend="○"
if [ "$count" -gt 100 ]; then
trend="●"
elif [ "$count" -gt 50 ]; then
trend="◐"
elif [ "$count" -gt 20 ]; then
trend="◑"
fi
dyad_table_add_row "filetypes" ".$ext" "$count" "$trend"
done <<< "$top_extensions"
rm -f "$temp_file"
}
# 获取提交时间分布
get_commit_timeline() {
local repo_path="$1"
local since="$2"
local until="$3"
dyad_log step "正在分析提交时间分布..."
cd "$repo_path"
local temp_file
temp_file=$(mktemp)
git log --since="$since" --until="$until" --pretty=format:"%ai" 2>/dev/null | \
cut -d' ' -f1 | \
sort | \
uniq -c | \
sort -k2 > "$temp_file"
# 定义表格
declare -A headers=(
["date"]="日期"
["commits"]="提交数"
["bar"]="趋势"
)
dyad_table_create "timeline" headers
local max_count=0
while IFS= read -r line; do
local count
count=$(echo "$line" | awk '{print $1}')
if [ "$count" -gt "$max_count" ]; then
max_count="$count"
fi
done < "$temp_file"
while IFS= read -r line; do
local count
local date
count=$(echo "$line" | awk '{print $1}')
date=$(echo "$line" | awk '{print $2}')
# 生成简单的条形图
local bar_length=$((count * 50 / max_count))
local bar
bar=$(printf "%${bar_length}s" | tr ' ' '█')
dyad_table_add_row "timeline" "$date" "$count" "$bar"
done < "$temp_file"
rm -f "$temp_file"
}
# JSON格式输出
output_json() {
local repo_path="$1"
local since="$2"
local until="$3"
local author="$4"
cd "$repo_path"
local git_args=("log" "--since=$since" "--until=$until" "--pretty=format:%H|%an|%ae|%ai|%s")
if [ -n "$author" ]; then
git_args+=("--author=$author")
fi
echo "{"
echo " \"repository\": \"$repo_path\","
echo " \"date_range\": {"
echo " \"since\": \"$since\","
echo " \"until\": \"$until\""
echo " },"
local total_commits
total_commits=$(git "${git_args[@]}" 2>/dev/null | wc -l)
echo " \"total_commits\": $total_commits,"
# 作者统计
echo " \"authors\": ["
git log --since="$since" --until="$until" --pretty=format:"%an" 2>/dev/null | \
sort | uniq -c | sort -rn | head -5 | \
while IFS= read -r line; do
local count
local author_name
count=$(echo "$line" | awk '{print $1}')
author_name=$(echo "$line" | awk '{print $2}')
echo " {\"name\": \"$author_name\", \"commits\": $count},"
done
echo " ]"
echo "}"
}
# 主函数
main() {
# 解析命令行参数
if ! dyad_parse "$@"; then
dyad_usage
exit 1
fi
# 显示帮助
if [ "$(dyad_param_get help)" = "true" ]; then
dyad_usage_set_header "GitStat v${VERSION} - Git仓库统计分析工具"
dyad_usage_set_footer "示例: gitstat -r /path/to/repo -a \"John\" -s 2024-01-01"
dyad_usage
exit 0
fi
# 获取参数值
local repo_path
repo_path=$(dyad_param_get repository)
local author
author=$(dyad_param_get author)
local since
since=$(dyad_param_get since)
local until
until=$(dyad_param_get until)
local output_format
output_format=$(dyad_param_get output)
# 显示头部
show_header
# 设置日志级别
if [ "$(dyad_param_get verbose)" = "true" ]; then
dyad_log_set_level debug
else
dyad_log_set_level info
fi
# 验证仓库
if ! validate_repository "$repo_path"; then
exit 1
fi
dyad_log info "统计时间范围: $since 至 $until"
echo ""
if [ "$output_format" = "json" ]; then
output_json "$repo_path" "$since" "$until" "$author"
else
# 获取提交统计
get_commit_stats "$repo_path" "$author" "$since" "$until"
echo ""
# 获取作者排名
get_author_leaderboard "$repo_path" "$since" "$until"
dyad_table_render "authors"
echo ""
# 获取文件类型统计
get_file_type_stats "$repo_path" "$since" "$until"
dyad_table_render "filetypes"
echo ""
# 获取时间分布
get_commit_timeline "$repo_path" "$since" "$until"
dyad_table_render "timeline"
echo ""
fi
dyad_log success "分析完成!"
}
# 执行主函数
main "$@"
GITSTAT_EOF
chmod +x gitstat.sh
使用示例
创建测试仓库并运行工具:
# 创建测试Git仓库
mkdir -p /tmp/test-repo
cd /tmp/test-repo
git init
# 创建一些测试提交
echo "initial" > README.md
git add README.md
git commit -m "Initial commit"
for i in {1..5}; do
echo "content $i" >> README.md
git add .
git commit -m "Update $i" --date="$(date -d "$i days ago" +%Y-%m-%d)"
done
# 回到项目目录
cd -
# 运行gitstat
./gitstat.sh -r /tmp/test-repo -s "2024-01-01"
# 显示帮助
./gitstat.sh --help
# JSON格式输出
./gitstat.sh -r /tmp/test-repo --output json
常见使用场景
dyad的设计考虑到了实际开发中的各种场景。以下是几个典型的应用示例:
场景一:自动化部署脚本
#!/usr/bin/env bash
source dyad.sh
# =============================================================================
# 自动化部署脚本
# =============================================================================
dyad_param_add --environment env -e "staging" required \
"部署环境" --type enum --choices dev staging production
dyad_param_add --version version -v "" \
"部署版本(留空使用最新)" --type string
dyad_param_add --skip-tests skip-tests -s false \
"跳过测试" --type boolean
dyad_task_create "deploy" "应用部署流程"
main() {
if ! dyad_parse "$@"; then
dyad_usage
exit 1
fi
local env
env=$(dyad_param_get environment)
local version
version=$(dyad_param_get version)
dyad_log info "开始部署到 $env 环境..."
# 部署逻辑...
}
main "$@"
场景二:数据处理管道
#!/usr/bin/env bash
source dyad.sh
# =============================================================================
# 数据处理管道
# =============================================================================
dyad_param_add --input input -i "" required \
"输入文件" --type file --exists
dyad_param_add --output output -o "" required \
"输出文件" --type file
dyad_param_add --filter filter -f "" \
"过滤条件" --type string
dyad_param_add --parallel parallel -p 4 \
"并行任务数" --type integer --min 1 --max 16
process_batch() {
local batch="$1"
local output="$2"
# 处理逻辑
echo "$batch" | processing_command >> "$output"
}
main() {
dyad_parse "$@"
local input
input=$(dyad_param_get input)
local output
output=$(dyad_param_get output)
dyad_log step "开始数据处理..."
# 分批处理
split -l 1000 "$input" /tmp/batch_
# ...并行处理...
dyad_log success "处理完成: $output"
}
main "$@"
场景三:系统健康检查工具
#!/usr/bin/env bash
source dyad.sh
check_disk_usage() {
local threshold="${1:-90}"
local usage
usage=$(df -h / | tail -1 | awk '{print $5}' | tr -d '%')
if [ "$usage" -gt "$threshold" ]; then
dyad_log error "磁盘使用率过高: ${usage}%"
return 1
fi
dyad_log success "磁盘使用率正常: ${usage}%"
return 0
}
check_memory() {
local available
available=$(free -m | grep Mem | awk '{print $7}')
if [ "$available" -lt 512 ]; then
dyad_log warn "可用内存不足: ${available}MB"
return 1
fi
dyad_log success "内存充足: ${available}MB"
return 0
}
check_services() {
local services=("nginx" "mysql" "redis")
for service in "${services[@]}"; do
if systemctl is-active --quiet "$service" 2>/dev/null; then
dyad_log success "服务运行正常: $service"
else
dyad_log error "服务异常: $service"
fi
done
}
main() {
dyad_log info "开始系统健康检查..."
check_disk_usage 80
check_memory
check_services
dyad_log info "健康检查完成"
}
main "$@"
最佳实践与技巧
脚本结构规范
遵循良好的脚本结构可以大大提高可维护性:
#!/usr/bin/env bash
# =============================================================================
# 脚本名称:example.sh
# 功能描述:简要描述脚本功能
# 使用方式:./example.sh [参数]
# 作者信息:作者 <email@example.com>
# 创建日期:2024-01-01
# 版本信息:1.0.0
# =============================================================================
set -euo pipefail # 严格模式
# =============================================================================
# 变量定义(常量大写,变量小写)
# =============================================================================
readonly SCRIPT_NAME="$(basename "$0")"
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly LOG_DIR="/var/log"
readonly CONFIG_DIR="/etc/${SCRIPT_NAME}"
# =============================================================================
# 依赖检查
# =============================================================================
check_dependencies() {
local deps=("curl" "jq" "git")
local missing=()
for dep in "${deps[@]}"; do
if ! command -v "$dep" &>/dev/null; then
missing+=("$dep")
fi
done
if [ ${#missing[@]} -gt 0 ]; then
dyad_log error "缺少依赖: ${missing[*]}"
return 1
fi
return 0
}
# =============================================================================
# 主函数
# =============================================================================
main() {
# 前置检查
check_dependencies || exit 1
# 主逻辑
# ...
}
# =============================================================================
# 入口点
# =============================================================================
main "$@"
错误处理策略
采用分级错误处理策略:
#!/usr/bin/env bash
source dyad.sh
# 设置错误处理回调
dyad_error_set_handler() {
local exit_code=$?
local line_num=$1
dyad_log error "脚本在第 ${line_num} 行以退出码 ${exit_code} 退出"
# 清理临时文件
[ -d "$TEMP_DIR" ] && rm -rf "$TEMP_DIR"
# 发送告警通知(可选)
# send_alert "Script failed at line $line_num"
exit "$exit_code"
}
# 启用错误追踪
trap 'dyad_error_set_handler $LINENO' ERR
# 使用try-catch风格的错误处理
safe_execute() {
local description="$1"
shift
local cmd="$@"
dyad_log step "$description..."
if eval "$cmd"; then
dyad_log success "$description - 完成"
return 0
else
dyad_log error "$description - 失败"
return 1
fi
}
# 使用示例
main() {
safe_execute "下载配置文件" curl -fsSL "$CONFIG_URL" -o config.json
safe_execute "验证配置" jq empty config.json
safe_execute "启动服务" systemctl start myservice
}
性能优化建议
对于大型脚本,注意性能优化:
#!/usr/bin/env bash
source dyad.sh
# =============================================================================
# 性能优化技巧
# =============================================================================
# 技巧1:使用局部变量
# 不推荐:
process_items_slow() {
for item in "${items[@]}"; do
echo "$item" # 每次都创建子进程
done
}
# 推荐:
process_items_fast() {
local item
for item in "${items[@]}"; do
echo "$item" # 局部变量避免全局查找
done
}
# 技巧2:避免子shell
# 不推荐:
count=$(echo "$large_string" | grep -c "pattern")
# 推荐:
count=$(printf '%s\n' "$large_string" | grep -c "pattern")
# 技巧3:使用内联操作代替管道
# 不推荐:
result=$(echo "$input" | sed 's/a/b/g' | awk '{print $1}')
# 推荐:
result=$(awk '{gsub(/a/, "b"); print $1}' <<< "$input")
# 技巧4:批量处理减少循环开销
# 不推荐:
for file in *.txt; do
process_file "$file"
done
# 推荐:
find . -name "*.txt" -exec process_file {} +
调试技巧
#!/usr/bin/env bash
source dyad.sh
# 启用调试模式
DEBUG=${DEBUG:-0}
debug() {
if [ "$DEBUG" = "1" ]; then
dyad_log debug "$*"
fi
}
# 追踪变量变化
trace_var() {
local var_name="$1"
debug "TRACE: ${var_name}=${!var_name}"
}
# 性能计时
time_it() {
local start_time
start_time=$(date +%s.%N)
"$@"
local end_time
end_time=$(date +%s.%N)
local elapsed
elapsed=$(echo "$end_time - $start_time" | bc)
dyad_log info "执行时间: ${elapsed}秒"
}
# 使用示例
main() {
DEBUG=1 time_it heavy_operation
}
总结与资源链接
通过这篇教程,我们系统地学习了 dyad-sh/dyad 这个现代化的Shell脚本框架。从环境搭建到核心功能,从基础用法到实战项目,你应该已经掌握了使用dyad构建专业级命令行工具的核心技能。
dyad的核心价值在于:将现代软件工程的最佳实践引入Shell脚本开发,让原本混乱的脚本变得结构清晰、可维护性强、功能完善。它特别适合以下场景:
- 需要频繁使用的运维脚本
- 对外提供服务的命令行工具
- 团队共享的自动化脚本
- 需要良好用户体验的工具
相关资源链接
- GitHub仓库:https://github.com/dyad-sh/dyad
- 官方文档:https://dyad.sh/docs
- 问题反馈:https://github.com/dyad-sh/dyad/issues
- 社区讨论:https://github.com/dyad-sh/dyad/discussions
推荐学习的相关项目
- Bash Boilerplate:另一个优秀的Bash脚本模板项目,提供了许多实用的脚本模式
- Google Shell Style Guide:Google发布的Shell编码规范,有助于编写更专业的脚本
- ShellCheck:静态分析工具,可以帮助你发现脚本中的问题
- httpie:现代的命令行HTTP客户端,展示了如何构建优秀的CLI工具
如果你觉得dyad对你有帮助,欢迎在GitHub上给项目一个Star,也可以参与贡献代码或文档。开源社区的进步离不开每个人的参与。
最后,动手实践是最好的学习方式。建议你选择一个自己日常工作中的痛点,尝试用dyad来重写那些曾经让你头疼的脚本。相信你会很快感受到dyad带来的效率提升。
评论区