AI摘要
六年前,当我第一次在终端里小心翼翼地敲下./startup.sh时,我完全没想到Shell脚本会变成我开发工具箱里最趁手的瑞士军刀。那时的我,正被一堆琐碎的重复操作困扰着——每次启动项目都要手动开启五个服务终端,每次切分支都要经历git status、git stash、git checkout的固定流程,每次部署测试都要重复敲一堆命令。
直到有天,我在第三次因为输错数据库端口号而调试失败时,终于忍无可忍。我盯着屏幕上那行需要反复输入的复杂命令,突然意识到:如果一个操作我需要做第三次,就应该把它自动化。
这就是我Shell脚本之旅的开端。今天,我想分享其中五个最常用、最救命的脚本,它们没有多高深的技术含量,但实实在在地把我的开发效率提升了至少30%。
脚本一:dev-env —— 一键启动本地开发环境
问题:每天浪费在“启动准备”上的15分钟
我们项目是个微服务架构,本地开发需要启动:数据库、Redis、注册中心、网关、业务服务A、业务服务B。每天早上的流程是这样的:
- 打开终端1,启动MySQL
- 打开终端2,启动Redis
- 打开终端3,启动注册中心
- ... 等所有依赖就绪
- 启动业务服务
- 祈祷所有服务都正常启动
最烦人的是,当某个服务启动失败时,我要在所有终端里翻找错误日志。
解决方案:一个知道“等待”和“检查”的脚本
#!/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 —— 数据库开发小助手
问题:开发时频繁重复的数据库操作
我需要经常:
- 重置测试数据库到某个初始状态
- 执行某个文件夹下的所有迁移脚本
- 导出表结构用于文档
- 快速查看某个表的数据量
每次都手动构造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,
tail -f logs/service-a.log - 打开终端2,
tail -f logs/service-b.log - 打开终端3,
tail -f logs/gateway.log - 在三个终端间来回切换,尝试拼凑完整的请求链路
解决方案:一个带颜色标识的聚合日志查看器
#!/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行,但它们节省了我无数个小时的重复劳动。更重要的是,它们改变了我的工作方式:
- 从被动到主动:我不再忍受低效流程,而是主动创造工具改善它
- 从模糊到精确:代码评审时有数据支持,讨论更聚焦
- 从个人到团队:我把这些脚本分享给团队,现在新同事也能一键启动环境
写这些脚本最大的收获不是脚本本身,而是一种思维转变:作为一名开发者,你的工作不是执行重复操作,而是消除重复操作。
Shell脚本的魅力在于它的直接和实用。你不需要搭建复杂的框架,不需要引入额外的依赖,只需要打开编辑器,解决眼前最痛的那个点。然后,你会发现自己拥有了更多时间,去解决那些真正需要创造力的难题。
下次当你发现自己第三次输入相同的命令序列时,停下来,花20分钟写个脚本。这可能是你一天中最有价值的20分钟。