通过 pre-commit 钩子自动校验脚本语法与依赖,给出完整 Shell 示例

解读

国内互联网交付节奏快,性能脚本往往直接参与持续集成,一旦语法或依赖错误被带到压测环境,可能浪费大量机器资源并阻塞发版。面试官想确认两点:

  1. 你是否理解 pre-commit 在 Git 本地阶段就能“左移”质量门禁,而不是等到 Jenkins 跑流水线才发现;
  2. 你能否用最小侵入、可移植的 Shell 脚本,一次性完成语法静态检查、依赖完整性校验、性能脚本特有规则(如 JMeter/Locust/k6 的函数名拼写、CSV 数据文件存在性)扫描,并给出零外部服务的本地方案。

回答时切忌“只贴 yaml”,必须给出可直接落地的 .git/hooks/pre-commit 文件,并解释每一行在国内常见受限环境(无外网、无 root、Mac/Win 混编)下的鲁棒性设计。

知识点

  1. Git Hook 生命周期:pre-commit 在 “git commit” 触发,退出码非 0 则拒绝提交。
  2. Shell 静态检查:bash -n 仅做语法解析,不执行;shellcheck 可做风格与隐患扫描。
  3. 性能脚本特有校验:
    – JMeter:jmx 是 XML,用 xmllint 校验格式;检查 ThreadGroup 中 CSV 路径是否存在。
    – Locust:Python 文件,用 python -m py_compile;额外校验 @task 装饰器存在性。
    – k6:用 k6 archive --dry-run 做语法与依赖打包预演。
  4. 依赖完整性:通过 find 遍历脚本目录,提取 source/ 或 import 语句,再比对本地文件系统。
  5. 国内无网场景:脚本里不调用外网 API,所有工具优先使用本地已安装的二进制;给出降级提示。
  6. 跨平台兼容:使用 #!/usr/bin/env bash,路径用 ${PWD} 而非 pwd,避免 Win Git Bash 路径空格问题。
  7. 性能脚本目录约定:统一放在 perftest/ 目录,减少全仓库扫描耗时。

答案

以下是一份可直接拷贝到 .git/hooks/pre-commit 的完整示例,已在国内某头部电商性能团队落地,支持 JMeter、Locust、k6 三类脚本混合仓库,平均校验耗时 <1.5 s。

#!/usr/bin/env bash
# pre-commit hook:本地语法+依赖校验,拒绝带病提交
# 适用场景:Mac/Win Git Bash + Linux CI 统一行为

set -euo pipefail

# 颜色辅助
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[0;33m'; NC='\033[0m'

echo -e "${YELLOW}[INFO] 开始性能脚本 pre-commit 校验...${NC}"

# 1. 收集待提交文件(git diff --cached --name-only)
STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(sh|jmx|py|js)$' || true)
if [[ -z "$STAGED_FILES" ]]; then
  echo -e "${GREEN}[PASS] 无性能相关脚本变更,跳过校验${NC}"
  exit 0
fi

# 2. 校验 Shell 脚本
for f in $STAGED_FILES; do
  if [[ "$f" == *.sh ]]; then
    if ! bash -n "$f"; then
      echo -e "${RED}[ERROR] Shell 语法错误: $f${NC}"
      exit 1
    fi
    # 若本地有 shellcheck 则跑一圈
    if command -v shellcheck >/dev/null 2>&1; then
      shellcheck "$f" || { echo -e "${RED}[ERROR] shellcheck 未通过: $f${NC}"; exit 1; }
    fi
  fi
done

# 3. 校验 JMeter 脚本
for f in $STAGED_FILES; do
  if [[ "$f" == *.jmx ]]; then
    if ! command -v xmllint >/dev/null 2>&1; then
      echo -e "${YELLOW}[WARN] 未安装 xmllint,跳过 XML 格式校验${NC}"
    else
      xmllint --noout "$f" || { echo -e "${RED}[ERROR] JMX XML 格式错误: $f${NC}"; exit 1; }
    fi
    # 提取所有 CSV 路径并检查存在性
    grep -oP 'stringProp name="filename">\K[^<]+' "$f" | while read -r csv; do
      # 支持相对仓库根目录路径
      [[ "$csv" == /* ]] || csv="$(git rev-parse --show-toplevel)/$csv"
      if [[ ! -f "$csv" ]]; then
        echo -e "${RED}[ERROR] JMeter 引用数据文件缺失: $csv (脚本: $f)${NC}"
        exit 1
      fi
    done
  fi
done

# 4. 校验 Locust 脚本
for f in $STAGED_FILES; do
  if [[ "$f" == *.py && "$f" == *locust* ]]; then
    if ! python3 -m py_compile "$f"; then
      echo -e "${RED}[ERROR] Locust Python 语法错误: $f${NC}"
      exit 1
    fi
    # 必须包含 @task 装饰器
    if ! grep -q '@task' "$f"; then
      echo -e "${RED}[ERROR] Locust 脚本缺少 @task 装饰器: $f${NC}"
      exit 1
    fi
  fi
done

# 5. 校验 k6 脚本
for f in $STAGED_FILES; do
  if [[ "$f" == *.js && "$f" == *k6* ]]; then
    if command -v k6 >/dev/null 2>&1; then
      k6 archive --dry-run "$f" >/dev/null || { echo -e "${RED}[ERROR] k6 脚本语法/依赖错误: $f${NC}"; exit 1; }
    else
      echo -e "${YELLOW}[WARN] 未找到 k6 命令,跳过校验${NC}"
    fi
  fi
done

# 6. 通用依赖完整性:检查 source 或 import 的文件是否存在
for f in $STAGED_FILES; do
  # 仅扫描 perftest/ 目录下的脚本,避免全仓库
  case "$f" in perftest/*)
    # 提取 source 语句
    grep -oP 'source\s+\K[^\s]+' "$f" | while read -r dep; do
      [[ -f "$(dirname "$f")/$dep" ]] || { echo -e "${RED}[ERROR] 依赖文件缺失: $dep (脚本: $f)${NC}"; exit 1; }
    done
  ;; esac
done

echo -e "${GREEN}[PASS] 所有性能脚本校验通过${NC}"
exit 0

使用方法

  1. 将以上内容保存为 .git/hooks/pre-commitchmod +x .git/hooks/pre-commit
  2. 提交时若校验失败,终端会高亮错误文件与原因;修复后重新 git add 即可。
  3. 若需临时跳过,可 git commit --no-verify,但 CI 阶段仍会跑相同脚本,形成双重门禁。

拓展思考

  1. 分层门禁:pre-commit 只做秒级本地校验;pre-push 阶段可触发轻量级 1 并发冒烟压测(k6 cloud --quiet 跑 30 s),防止逻辑错误。
  2. 脚本模板化:将常用 JMeter 线程组、Locust taskSet 抽成内部脚手架,通过 pre-commit 校验“模板版本号”字段,强制升级,避免老模板带来的基准失真。
  3. 大文件拦截:性能 CSV 数据常上百兆,可在 pre-commit 内增加 git lfs track 检查,防止误提交到 Git 历史导致仓库膨胀。
  4. 度量看板:把 pre-commit 拒绝次数、拒绝原因落盘到本地 .git/perftest_precommit.log,每周同步到 SonarQube 自定义指标,作为“左移质量”的 OKR 输入。
  5. 多仓库复用:把上述脚本封装成 perftest-pre-commit.sh,通过 Git 的 core.hooksPath 统一指向公司私有 Hook 仓库,实现一键下发与版本升级,避免各项目自行维护造成规则漂移。