AI摘要

作者分享五个自写Shell脚本:一键启停微服务、智能切分支、数据库助手、聚合日志、代码统计,把重复操作自动化,日均省30%时间,并强调“第三次就脚本化”的思维转变。

六年前,当我第一次在终端里小心翼翼地敲下./startup.sh时,我完全没想到Shell脚本会变成我开发工具箱里最趁手的瑞士军刀。那时的我,正被一堆琐碎的重复操作困扰着——每次启动项目都要手动开启五个服务终端,每次切分支都要经历git statusgit stashgit checkout的固定流程,每次部署测试都要重复敲一堆命令。

直到有天,我在第三次因为输错数据库端口号而调试失败时,终于忍无可忍。我盯着屏幕上那行需要反复输入的复杂命令,突然意识到:如果一个操作我需要做第三次,就应该把它自动化。

这就是我Shell脚本之旅的开端。今天,我想分享其中五个最常用、最救命的脚本,它们没有多高深的技术含量,但实实在在地把我的开发效率提升了至少30%。

脚本一:dev-env —— 一键启动本地开发环境

问题:每天浪费在“启动准备”上的15分钟

我们项目是个微服务架构,本地开发需要启动:数据库、Redis、注册中心、网关、业务服务A、业务服务B。每天早上的流程是这样的:

  1. 打开终端1,启动MySQL
  2. 打开终端2,启动Redis
  3. 打开终端3,启动注册中心
  4. ... 等所有依赖就绪
  5. 启动业务服务
  6. 祈祷所有服务都正常启动

最烦人的是,当某个服务启动失败时,我要在所有终端里翻找错误日志。

解决方案:一个知道“等待”和“检查”的脚本

#!/bin/bash
# dev-env.sh - 一键启动本地开发环境
# 作者:一个不想每天重复敲命令的程序员

set -e  # 遇到任何错误就退出

echo "🚀 开始启动本地开发环境..."
echo "========================================"

# 定义颜色输出函数
green() { echo -e "\033[32m$1\033[0m"; }
red() { echo -e "\033[31m$1\033[0m"; }
blue() { echo -e "\033[34m$1\033[0m"; }

# 1. 检查必要依赖
check_dependency() {
    if ! command -v $1 &> /dev/null; then
        red "❌ 未找到 $1,请先安装"
        exit 1
    fi
}

check_dependency "docker"
check_dependency "docker-compose"
check_dependency "java"

# 2. 启动基础设施(数据库、缓存等)
blue "📦 启动基础设施..."
docker-compose -f docker-compose-infra.yml up -d

# 等待MySQL真正可用(而不是容器启动就完事)
blue "⏳ 等待MySQL就绪(最多30秒)..."
timeout=30
while ! docker exec mysql_dev mysqladmin ping -h localhost --silent; do
    sleep 1
    timeout=$((timeout-1))
    if [ $timeout -eq 0 ]; then
        red "❌ MySQL启动超时"
        docker-compose -f docker-compose-infra.yml logs mysql
        exit 1
    fi
done
green "✅ MySQL已就绪"

# 3. 按顺序启动微服务
services=("service-registry" "api-gateway" "user-service" "order-service")

for service in "${services[@]}"; do
    blue "🚀 启动 $service..."
    
    # 进入服务目录
    cd "$service" || { red "❌ 找不到目录: $service"; exit 1; }
    
    # 使用Maven启动(我们项目用Spring Boot)
    nohup mvn spring-boot:run > "../logs/$service.log" 2>&1 &
    
    # 记录进程ID,方便后续管理
    echo $! > "../logs/$service.pid"
    
    # 等待几秒,避免同时启动造成资源竞争
    sleep 3
    
    # 检查启动日志中是否有错误
    if tail -5 "../logs/$service.log" | grep -q "ERROR"; then
        red "❌ $service 启动可能失败,请检查日志"
        echo "最后5行日志:"
        tail -5 "../logs/$service.log"
    else
        green "✅ $service 启动中(查看日志:tail -f logs/$service.log)"
    fi
    
    cd ..
done

# 4. 生成一个便捷的命令备忘单
blue "\n📋 环境启动完成!以下是一些有用命令:"
echo "----------------------------------------"
echo "查看所有日志:    tail -f logs/*.log"
echo "查看用户服务日志:tail -f logs/user-service.log"
echo "停止所有服务:    ./stop-env.sh"
echo "重启用户服务:    ./restart-service.sh user-service"
echo "----------------------------------------"

green "\n🎉 开发环境准备就绪!开始愉快地编码吧~"

这个脚本教会我的:自动化不仅仅是执行命令,还要包含“健康检查”和“友好提示”。那个等待MySQL真正可用的循环,是从无数次“服务启动失败因为数据库还没准备好”的教训中得来的。

脚本二:git-switcher —— 智能分支切换器

问题:Git分支切换的“仪式感”太重

我们的Git工作流要求功能开发在独立分支进行。每天我可能在3-4个分支间切换:

# 常规操作流程
git status          # 看看有没有未提交的
git stash           # 暂存起来
git checkout main   # 切到主分支
git pull            # 更新
git checkout feature/xxx  # 切到功能分支
git stash pop       # 恢复修改

每次切换都要重复这个仪式,有时候忘记git status就直接stash,结果把不该暂存的东西暂存了。

解决方案:一个理解上下文的分支切换助手

#!/bin/bash
# git-switcher.sh - 智能分支切换
# 记住:这个脚本简化了操作,但也保留了可控性

target_branch="$1"

if [ -z "$target_branch" ]; then
    echo "用法: $0 <分支名>"
    echo "示例: $0 feature/user-login"
    exit 1
fi

current_branch=$(git branch --show-current)
echo "当前分支: $current_branch"
echo "目标分支: $target_branch"

# 检查目标分支是否存在(本地或远程)
if ! git show-ref --verify --quiet "refs/heads/$target_branch"; then
    echo "⚠️  本地不存在分支 $target_branch"
    
    # 检查远程是否存在
    if git ls-remote --exit-code --heads origin "$target_branch" >/dev/null 2>&1; then
        read -p "远程存在该分支,是否拉取到本地?(y/N): " -n 1 -r
        echo
        if [[ $REPLY =~ ^[Yy]$ ]]; then
            git fetch origin "$target_branch:$target_branch"
            echo "✅ 已拉取远程分支到本地"
        else
            echo "操作取消"
            exit 0
        fi
    else
        read -p "分支不存在,是否创建新分支?(y/N): " -n 1 -r
        echo
        if [[ $REPLY =~ ^[Yy]$ ]]; then
            git checkout -b "$target_branch"
            echo "✅ 已创建并切换到新分支 $target_branch"
            exit 0
        else
            echo "操作取消"
            exit 0
        fi
    fi
fi

# 检查当前工作区状态
if [ -n "$(git status --porcelain)" ]; then
    echo "📝 检测到未提交的更改"
    
    # 显示有哪些文件被修改了
    echo "修改的文件:"
    git status --porcelain | while read -r status file; do
        case "$status" in
            "M") echo "  修改: $file" ;;
            "A") echo "  新增: $file" ;;
            "D") echo "  删除: $file" ;;
            "R") echo "  重命名: $file" ;;
            "??") echo "  未跟踪: $file" ;;
        esac
    done
    
    echo ""
    echo "请选择操作:"
    echo "1) 暂存所有更改并切换"
    echo "2) 提交更改并切换(需要输入提交信息)"
    echo "3) 放弃所有更改并切换(谨慎!)"
    echo "4) 取消切换"
    
    read -p "选择 (1-4): " choice
    
    case $choice in
        1)
            git stash push -m "自动暂存于 $(date '+%Y-%m-%d %H:%M:%S')"
            stash_saved=true
            echo "✅ 更改已暂存"
            ;;
        2)
            git add .
            read -p "请输入提交信息: " commit_msg
            if [ -z "$commit_msg" ]; then
                commit_msg="WIP: 临时提交于 $(date '+%Y-%m-%d %H:%M:%S')"
            fi
            git commit -m "$commit_msg"
            echo "✅ 更改已提交"
            ;;
        3)
            read -p "确定要放弃所有更改吗?(y/N): " -n 1 -r
            echo
            if [[ $REPLY =~ ^[Yy]$ ]]; then
                git checkout .
                echo "✅ 更改已放弃"
            else
                echo "操作取消"
                exit 0
            fi
            ;;
        4)
            echo "操作取消"
            exit 0
            ;;
        *)
            echo "无效选择"
            exit 1
            ;;
    esac
fi

# 执行切换
echo "🔄 切换到分支 $target_branch..."
git checkout "$target_branch"

# 如果切换成功,尝试拉取最新代码
if [ $? -eq 0 ]; then
    echo "📥 拉取最新更改..."
    git pull --ff-only
    
    # 如果之前暂存了,尝试恢复
    if [ "$stash_saved" = true ]; then
        echo "📤 恢复暂存的更改..."
        if git stash pop; then
            echo "✅ 更改已恢复"
        else
            echo "⚠️  恢复暂存时出现冲突,请手动解决"
            echo "   使用 git stash list 查看暂存列表"
            echo "   使用 git stash show -p stash@{0} 查看暂存内容"
        fi
    fi
    
    green "✅ 已切换到 $target_branch 分支"
    echo ""
    echo "最近3次提交:"
    git log --oneline -3
else
    red "❌ 切换分支失败"
fi

这个脚本教会我的:好的工具不是替你做所有决定,而是给你足够的信息和选择权。我保留了手动选择的选项,因为有时候我需要决定是提交、暂存还是放弃。

脚本三:db-helper —— 数据库开发小助手

问题:开发时频繁重复的数据库操作

我需要经常:

  1. 重置测试数据库到某个初始状态
  2. 执行某个文件夹下的所有迁移脚本
  3. 导出表结构用于文档
  4. 快速查看某个表的数据量

每次都手动构造MySQL命令,参数一多就容易错。

解决方案:一个集常用操作于一身的工具

#!/bin/bash
# db-helper.sh - 数据库开发助手
# 注意:这个脚本包含数据库密码,应该放在安全的地方或使用环境变量

# 配置(实际使用中应该从环境变量读取)
DB_HOST="localhost"
DB_PORT="3306"
DB_NAME="myapp_dev"
DB_USER="dev_user"
# 警告:密码不应该硬编码在脚本中!
# 更好的做法是使用 ~/.my.cnf 或环境变量
DB_PASS="dev_password"  # 这只是一个示例

# 使用安全的密码获取方式(如果可用)
if [ -f ~/.db_password ]; then
    DB_PASS=$(cat ~/.db_password | tr -d '\n')
fi

mysql_cmd="mysql -h$DB_HOST -P$DB_PORT -u$DB_USER -p$DB_PASS $DB_NAME"

# 显示用法
show_help() {
    echo "数据库开发助手"
    echo "========================"
    echo "用法: $0 <命令> [参数]"
    echo ""
    echo "命令:"
    echo "  reset          重置数据库到初始状态"
    echo "  migrate        执行 migrations/ 下的所有脚本"
    echo "  schema [表名]  导出表结构(不指定表名则导出所有)"
    echo "  count [表名]   查看表的行数"
    echo "  sample [表名]  查看表的10条样本数据"
    echo "  query \"SQL\"   执行自定义SQL查询"
    echo "  backup [文件]  备份数据库到文件"
    echo ""
    echo "示例:"
    echo "  $0 reset"
    echo "  $0 schema users"
    echo "  $0 query \"SELECT * FROM users WHERE id = 1\""
}

# 检查数据库连接
check_connection() {
    if ! echo "SELECT 1;" | $mysql_cmd 2>/dev/null; then
        echo "❌ 无法连接到数据库"
        echo "请检查:"
        echo "  1. 数据库服务是否运行"
        echo "  2. 连接参数是否正确"
        echo "  3. 用户权限是否足够"
        exit 1
    fi
}

case "$1" in
    "reset")
        echo "⚠️  即将重置数据库 $DB_NAME,所有数据将被清除!"
        read -p "确认吗?(输入 'YES' 继续): " confirmation
        
        if [ "$confirmation" != "YES" ]; then
            echo "操作取消"
            exit 0
        fi
        
        echo "1. 删除数据库..."
        echo "DROP DATABASE IF EXISTS $DB_NAME; CREATE DATABASE $DB_NAME;" | mysql -h$DB_HOST -P$DB_PORT -u$DB_USER -p$DB_PASS
        
        echo "2. 创建基础表结构..."
        if [ -f "sql/schema.sql" ]; then
            $mysql_cmd < sql/schema.sql
            echo "✅ 数据库已重置"
        else
            echo "❌ 找不到 schema.sql 文件"
            exit 1
        fi
        
        echo "3. 导入基础数据..."
        if [ -f "sql/seed_data.sql" ]; then
            $mysql_cmd < sql/seed_data.sql
            echo "✅ 基础数据已导入"
        fi
        ;;
        
    "migrate")
        check_connection
        
        if [ ! -d "migrations" ]; then
            echo "❌ 找不到 migrations 目录"
            exit 1
        fi
        
        echo "执行数据库迁移..."
        
        # 按文件名排序执行
        for file in $(ls migrations/*.sql | sort); do
            echo "执行: $file"
            $mysql_cmd < "$file"
            
            if [ $? -eq 0 ]; then
                echo "✅ 成功"
            else
                echo "❌ 失败"
                exit 1
            fi
        done
        
        echo "✅ 所有迁移执行完成"
        ;;
        
    "schema")
        check_connection
        
        table_name="$2"
        
        if [ -z "$table_name" ]; then
            echo "导出所有表结构..."
            $mysql_cmd --skip-column-names -e "SHOW TABLES" | while read -r table; do
                echo "=== $table ==="
                $mysql_cmd -e "SHOW CREATE TABLE \`$table\`\G" | tail -n +2
                echo ""
            done
        else
            echo "导出表结构: $table_name"
            $mysql_cmd -e "SHOW CREATE TABLE \`$table_name\`\G"
        fi
        ;;
        
    "count")
        check_connection
        
        table_name="$2"
        
        if [ -z "$table_name" ]; then
            echo "请指定表名"
            echo "用法: $0 count <表名>"
            exit 1
        fi
        
        echo "表 $table_name 的行数:"
        $mysql_cmd -e "SELECT COUNT(*) as count FROM \`$table_name\`"
        ;;
        
    "sample")
        check_connection
        
        table_name="$2"
        
        if [ -z "$table_name" ]; then
            echo "请指定表名"
            echo "用法: $0 sample <表名>"
            exit 1
        fi
        
        echo "表 $table_name 的10条样本数据:"
        $mysql_cmd -e "SELECT * FROM \`$table_name\` LIMIT 10"
        ;;
        
    "query")
        check_connection
        
        sql="$2"
        
        if [ -z "$sql" ]; then
            echo "请提供SQL查询语句"
            echo "用法: $0 query \"SQL语句\""
            exit 1
        fi
        
        echo "执行查询: $sql"
        echo "------------------------"
        $mysql_cmd -e "$sql"
        ;;
        
    "backup")
        check_connection
        
        backup_file="${2:-backup_$(date +%Y%m%d_%H%M%S).sql}"
        
        echo "备份数据库到: $backup_file"
        mysqldump -h$DB_HOST -P$DB_PORT -u$DB_USER -p$DB_PASS $DB_NAME > "$backup_file"
        
        if [ $? -eq 0 ]; then
            echo "✅ 备份完成,文件大小: $(du -h "$backup_file" | cut -f1)"
        else
            echo "❌ 备份失败"
            exit 1
        fi
        ;;
        
    *)
        show_help
        ;;
esac

这个脚本教会我的:安全性很重要!最初的版本密码是硬编码的,后来我意识到这有多危险。现在我把敏感信息移到外部文件或环境变量中。另外,对于危险操作(如reset),一定要有确认机制。

脚本四:log-watcher —— 多服务日志聚合查看器

问题:调试时需要同时看多个服务的日志

微服务调试的噩梦:用户请求失败,我需要:

  1. 打开终端1,tail -f logs/service-a.log
  2. 打开终端2,tail -f logs/service-b.log
  3. 打开终端3,tail -f logs/gateway.log
  4. 在三个终端间来回切换,尝试拼凑完整的请求链路

解决方案:一个带颜色标识的聚合日志查看器

#!/bin/bash
# log-watcher.sh - 多服务日志聚合查看
# 给每个服务的日志加上颜色,方便区分

# 配置要监控的服务日志
declare -A LOG_FILES=(
    ["网关"]="logs/gateway.log"
    ["用户服务"]="logs/user-service.log"
    ["订单服务"]="logs/order-service.log"
    ["商品服务"]="logs/product-service.log"
    ["支付服务"]="logs/payment-service.log"
)

# 颜色定义
declare -A COLORS=(
    ["网关"]="\033[1;36m"      # 青色
    ["用户服务"]="\033[1;32m"  # 绿色
    ["订单服务"]="\033[1;33m"  # 黄色
    ["商品服务"]="\033[1;35m"  # 紫色
    ["支付服务"]="\033[1;31m"  # 红色
    ["RESET"]="\033[0m"
)

# 检查所有日志文件是否存在
check_log_files() {
    for service in "${!LOG_FILES[@]}"; do
        log_file="${LOG_FILES[$service]}"
        
        if [ ! -f "$log_file" ]; then
            echo "⚠️  日志文件不存在: $log_file"
            read -p "是否创建空文件?(y/N): " -n 1 -r
            echo
            if [[ $REPLY =~ ^[Yy]$ ]]; then
                mkdir -p "$(dirname "$log_file")"
                touch "$log_file"
                echo "✅ 已创建: $log_file"
            else
                # 从数组中移除不存在的文件
                unset LOG_FILES["$service"]
                unset COLORS["$service"]
            fi
        fi
    done
}

# 显示帮助
show_help() {
    echo "多服务日志聚合查看器"
    echo "========================"
    echo "用法: $0 [选项]"
    echo ""
    echo "选项:"
    echo "  -f, --follow       实时跟踪日志(默认)"
    echo "  -n, --lines N      显示最后N行日志"
    echo "  -s, --service S    只显示特定服务的日志"
    echo "  -g, --grep PATTERN 只显示包含特定模式的日志"
    echo "  --no-color         禁用颜色输出"
    echo "  -h, --help         显示此帮助"
    echo ""
    echo "示例:"
    echo "  $0                    # 实时查看所有日志"
    echo "  $0 -n 50             # 显示最后50行日志"
    echo "  $0 -s \"用户服务\"      # 只查看用户服务日志"
    echo "  $0 -g \"ERROR\"       # 只查看包含ERROR的日志"
    echo ""
    echo "可用服务: ${!LOG_FILES[*]}"
}

# 解析参数
MODE="follow"
LINES=50
SERVICE_FILTER=""
GREP_PATTERN=""
USE_COLOR=true

while [[ $# -gt 0 ]]; do
    case $1 in
        -f|--follow)
            MODE="follow"
            shift
            ;;
        -n|--lines)
            LINES="$2"
            MODE="tail"
            shift 2
            ;;
        -s|--service)
            SERVICE_FILTER="$2"
            shift 2
            ;;
        -g|--grep)
            GREP_PATTERN="$2"
            shift 2
            ;;
        --no-color)
            USE_COLOR=false
            shift
            ;;
        -h|--help)
            show_help
            exit 0
            ;;
        *)
            echo "未知选项: $1"
            show_help
            exit 1
            ;;
    esac
done

# 检查日志文件
check_log_files

if [ ${#LOG_FILES[@]} -eq 0 ]; then
    echo "❌ 没有可用的日志文件"
    exit 1
fi

# 打印标题
echo "🔍 开始查看日志..."
echo "模式: $MODE"
if [ -n "$SERVICE_FILTER" ]; then
    echo "服务过滤: $SERVICE_FILTER"
fi
if [ -n "$GREP_PATTERN" ]; then
    echo "内容过滤: $GREP_PATTERN"
fi
echo "========================"

# 根据模式执行
if [ "$MODE" = "tail" ]; then
    # 显示最后N行
    for service in "${!LOG_FILES[@]}"; do
        if [ -n "$SERVICE_FILTER" ] && [ "$service" != "$SERVICE_FILTER" ]; then
            continue
        fi
        
        log_file="${LOG_FILES[$service]}"
        
        if [ "$USE_COLOR" = true ]; then
            color="${COLORS[$service]}"
            reset="${COLORS[RESET]}"
            echo "${color}=== $service ===${reset}"
        else
            echo "=== $service ==="
        fi
        
        if [ -n "$GREP_PATTERN" ]; then
            tail -n "$LINES" "$log_file" | grep --color=always "$GREP_PATTERN" || true
        else
            tail -n "$LINES" "$log_file"
        fi
        
        echo ""
    done
    
else
    # 实时跟踪模式
    # 使用命名管道来实现多路日志聚合
    PIPE_DIR="/tmp/log-watcher-$$"
    mkdir -p "$PIPE_DIR"
    
    # 为每个日志文件创建跟踪进程
    for service in "${!LOG_FILES[@]}"; do
        if [ -n "$SERVICE_FILTER" ] && [ "$service" != "$SERVICE_FILTER" ]; then
            continue
        fi
        
        log_file="${LOG_FILES[$service]}"
        pipe_file="$PIPE_DIR/$service.pipe"
        
        mkfifo "$pipe_file"
        
        # 启动跟踪进程
        (
            if [ "$USE_COLOR" = true ]; then
                color="${COLORS[$service]}"
                reset="${COLORS[RESET]}"
                
                if [ -n "$GREP_PATTERN" ]; then
                    tail -f "$log_file" | grep --line-buffered "$GREP_PATTERN" | \
                    while read -r line; do
                        echo "${color}[$service]${reset} $line"
                    done
                else
                    tail -f "$log_file" | \
                    while read -r line; do
                        echo "${color}[$service]${reset} $line"
                    done
                fi
            else
                if [ -n "$GREP_PATTERN" ]; then
                    tail -f "$log_file" | grep --line-buffered "$GREP_PATTERN" | \
                    while read -r line; do
                        echo "[$service] $line"
                    done
                else
                    tail -f "$log_file" | \
                    while read -r line; do
                        echo "[$service] $line"
                    done
                fi
            fi
        ) > "$pipe_file" &
        
        PIDS+=($!)
    done
    
    # 合并所有管道的输出
    echo "📊 开始实时日志追踪 (按 Ctrl+C 退出)..."
    echo "========================"
    
    # 使用多路复用读取
    for pipe in "$PIPE_DIR"/*.pipe; do
        cat "$pipe" &
        CAT_PIDS+=($!)
    done
    
    # 等待中断信号
    trap 'cleanup' INT TERM
    
    cleanup() {
        echo -e "\n🛑 停止日志追踪..."
        
        # 杀死所有子进程
        kill "${PIDS[@]}" 2>/dev/null
        kill "${CAT_PIDS[@]}" 2>/dev/null
        
        # 清理管道文件
        rm -rf "$PIPE_DIR"
        
        exit 0
    }
    
    # 等待所有cat进程
    wait "${CAT_PIDS[@]}"
fi

这个脚本教会我的:用户体验很重要。给不同服务的日志加上颜色,让调试时一目了然。另外,处理并发(多个tail -f进程)和信号(Ctrl+C退出)需要小心。

脚本五:code-stats —— 代码库统计与质量检查器

问题:代码评审时缺乏客观数据

代码评审时经常会有这样的对话:

  • "这个类太大了,应该拆分"
  • "我觉得还好啊,不算大"
  • "这里面重复代码有点多"
  • "哪里重复了?"

全是主观感受,没有客观数据。

解决方案:用数据说话的分析脚本

#!/bin/bash
# code-stats.sh - 代码库统计与质量检查
# 在代码评审前运行,提供客观数据

set -e

echo "📊 代码分析报告"
echo "生成时间: $(date)"
echo "========================================"

# 1. 基本统计
echo ""
echo "一、代码库基本信息"
echo "-------------------"

total_files=$(find . -name "*.java" -type f | wc -l)
total_lines=$(find . -name "*.java" -type f -exec cat {} \; | wc -l)

echo "Java文件数: $total_files"
echo "总行数: $total_lines"

if [ $total_files -gt 0 ]; then
    avg_lines=$((total_lines / total_files))
    echo "平均每个文件行数: $avg_lines"
fi

# 2. 大文件预警(超过400行的类)
echo ""
echo "二、大文件预警(>400行)"
echo "-------------------"

large_files=0
while IFS= read -r file; do
    lines=$(wc -l < "$file")
    if [ $lines -gt 400 ]; then
        echo "$file: $lines 行"
        large_files=$((large_files + 1))
    fi
done < <(find . -name "*.java" -type f)

if [ $large_files -eq 0 ]; then
    echo "✅ 没有超过400行的Java文件"
else
    echo "⚠️  发现 $large_files 个文件可能过大,建议考虑拆分"
fi

# 3. 方法长度检查(超过50行的方法)
echo ""
echo "三、长方法预警(>50行)"
echo "-------------------"

# 这个检查相对复杂,我们用简单的方法检测大括号
long_methods=0
for file in $(find . -name "*.java" -type f); do
    # 简单的检测:统计每对大括号之间的行数
    line_num=0
    in_method=false
    method_start=0
    method_name=""
    
    while IFS= read -r line; do
        line_num=$((line_num + 1))
        
        # 检测方法开始(简化版,实际应该用更准确的解析)
        if [[ "$line" =~ "public "|"private "|"protected " ]] && \
           [[ "$line" =~ "(" ]] && [[ "$line" =~ ")" ]] && \
           [[ "$line" =~ "{"$ ]] && ! [[ "$line" =~ "class "|"interface " ]]; then
            if [ "$in_method" = false ]; then
                in_method=true
                method_start=$line_num
                method_name=$(echo "$line" | sed 's/{.*//' | xargs)
            fi
        fi
        
        # 检测方法结束
        if [ "$in_method" = true ] && [[ "$line" =~ ^[[:space:]]*}$ ]]; then
            method_end=$line_num
            method_length=$((method_end - method_start + 1))
            
            if [ $method_length -gt 50 ]; then
                echo "$file 中的方法可能过长:"
                echo "  方法: $method_name"
                echo "  位置: 第 $method_start-$method_end 行"
                echo "  长度: $method_length 行"
                echo ""
                long_methods=$((long_methods + 1))
            fi
            
            in_method=false
        fi
    done < "$file"
done

if [ $long_methods -eq 0 ]; then
    echo "✅ 未发现明显过长的方法"
else
    echo "⚠️  发现 $long_methods 个可能过长的方法,建议重构"
fi

# 4. TODO/FIXME检查
echo ""
echo "四、待办事项检查"
echo "-------------------"

todo_count=$(grep -r "TODO" . --include="*.java" | grep -v ".git" | wc -l)
fixme_count=$(grep -r "FIXME" . --include="*.java" | grep -v ".git" | wc -l)
xxx_count=$(grep -r "XXX" . --include="*.java" | grep -v ".git" | wc -l)

echo "TODO 数量: $todo_count"
echo "FIXME 数量: $fixme_count"
echo "XXX 数量: $xxx_count"

if [ $((todo_count + fixme_count + xxx_count)) -gt 10 ]; then
    echo "⚠️  待办事项较多,建议定期清理"
    
    echo ""
    echo "最近添加的TODO(按文件修改时间排序):"
    find . -name "*.java" -type f -exec grep -l "TODO" {} \; | \
    xargs ls -lt | head -5
fi

# 5. 重复代码初步检测(通过相同行数)
echo ""
echo "五、重复代码检测(初步)"
echo "-------------------"

# 创建一个临时目录
TEMP_DIR=$(mktemp -d)

# 提取所有方法(简化版)
method_count=0
declare -A method_hashes

for file in $(find . -name "*.java" -type f); do
    # 提取方法内容,计算简单哈希
    awk '
    /public |private |protected / && /\(.*\)/ && !/class |interface / {
        if (in_method) {
            # 输出上一个方法
            print method_body
            print "===END==="
        }
        in_method=1
        method_body=$0
        next
    }
    in_method {
        method_body=method_body "\n" $0
        if (/^[[:space:]]*}/) {
            print method_body
            print "===END==="
            in_method=0
        }
    }
    ' "$file" > "$TEMP_DIR/methods.txt"
    
    # 处理提取到的方法
    current_method=""
    while IFS= read -r line; do
        if [ "$line" = "===END===" ]; then
            if [ -n "$current_method" ]; then
                # 计算哈希(简单版本,按行排序后计算)
                hash=$(echo "$current_method" | sort | md5sum | cut -d' ' -f1)
                
                if [ -n "${method_hashes[$hash]}" ]; then
                    echo "发现相似方法:"
                    echo "  哈希: $hash"
                    echo "  内容预览:"
                    echo "$current_method" | head -3 | sed 's/^/    /'
                    echo ""
                else
                    method_hashes[$hash]=1
                fi
                
                current_method=""
            fi
        else
            current_method="$current_method"$'\n'"$line"
        fi
    done < "$TEMP_DIR/methods.txt"
done

# 清理临时文件
rm -rf "$TEMP_DIR"

echo "✅ 重复代码检测完成(基于方法内容哈希)"

# 6. 总结报告
echo ""
echo "========================================"
echo "📈 分析总结"
echo "========================================"

echo "1. 基础规模: $total_files 个文件, $total_lines 行代码"
echo "2. 大文件: $large_files 个文件超过400行"
echo "3. 长方法: $long_methods 个方法可能超过50行"
echo "4. 待办事项: $todo_count TODO, $fixme_count FIXME"

if [ $large_files -gt 5 ] || [ $long_methods -gt 10 ]; then
    echo ""
    echo "⚠️  代码健康度警告: 发现较多大文件和长方法,建议考虑重构"
elif [ $((todo_count + fixme_count)) -gt 20 ]; then
    echo ""
    echo "📝 建议: 待办事项较多,建议安排时间集中处理"
else
    echo ""
    echo "✅ 代码整体健康状况良好"
fi

echo ""
echo "🔍 建议下一步操作:"
echo "  - 对于大文件,考虑按职责拆分"
echo "  - 对于长方法,提取为多个小方法"
echo "  - 安排时间处理高优先级的TODO/FIXME"

这个脚本教会我的:自动化分析可以提供客观的代码质量视图,但工具不能替代人的判断。这个脚本的输出只是参考,真正的代码评审还需要结合业务上下文和设计意图。

写在最后:Shell脚本给我的不只是效率

这五个脚本,每个都不到200行,但它们节省了我无数个小时的重复劳动。更重要的是,它们改变了我的工作方式:

  1. 从被动到主动:我不再忍受低效流程,而是主动创造工具改善它
  2. 从模糊到精确:代码评审时有数据支持,讨论更聚焦
  3. 从个人到团队:我把这些脚本分享给团队,现在新同事也能一键启动环境

写这些脚本最大的收获不是脚本本身,而是一种思维转变:作为一名开发者,你的工作不是执行重复操作,而是消除重复操作

Shell脚本的魅力在于它的直接和实用。你不需要搭建复杂的框架,不需要引入额外的依赖,只需要打开编辑器,解决眼前最痛的那个点。然后,你会发现自己拥有了更多时间,去解决那些真正需要创造力的难题。

下次当你发现自己第三次输入相同的命令序列时,停下来,花20分钟写个脚本。这可能是你一天中最有价值的20分钟。

版权声明 ▶ 本网站名称:黄磊的博客
▶ 本文标题:Shell脚本拯救重复劳动:我为自己编写的五个高效开发脚本
▶ 本文链接:https://www.huangleicole.com/experience_summary/89.html
▶ 转载本站文章需要遵守:商业转载请联系站长,非商业转载请注明出处!!

如果觉得我的文章对你有用,请随意赞赏