#!/usr/bin/env sh # merge-findings.sh — 合併多組審查發現,依位置去重,再依嚴重度排序。 # 用法:merge-findings.sh [發現檔…] # 給檔名就讀那幾個檔,不給檔名就讀 stdin。code-review 六組與 api-doc 兩個面向 # 共用這一支,兩支技能都不再自己描述合併與排序規則。 # # 輸入格式(TSV,一列一筆發現,六欄,欄位之間一個 tab): # 1 file 檔案路徑,相對專案根目錄,不得留空 # 2 line 行號,正整數 # 3 severity 嚴重度,只收 高、中、低 # 4 group 組別或面向代號,例如 2 或 1 狀態碼 # 5 item 發現名稱,壞味道名稱或稽核項目名稱 # 6 detail 一句證據加上建議修法 # 空白列、開頭是 # 的列、第一欄為「無發現」的列一律略過。 # # 輸出(TSV,同樣六欄,一個位置一列,印到 stdout): # file 與 line 相同的發現合併成一列。 # severity 取該位置最高的一級。group、item、detail 依輸入順序以「|」相連。 # 排序:嚴重度 高 → 中 → 低;同一級再依 file 字典序、line 數值遞增。 # # 結束碼: # 0 合併成功,至少印出一列 # 1 沒有任何發現(輸入沒有可用的資料列),呼叫端回報「無發現」 # 2 參數或環境錯誤(找不到輸入檔,或缺 awk、sort、cut) # 3 輸入格式錯誤(欄數不是 6、行號不是正整數,或嚴重度不在 高、中、低) # # 護欄: # 格式錯誤一律 exit 3 並在 stderr 指出是第幾個檔的第幾列,不猜、不放行、不自行補欄。 # 錯誤訊息一律印繁中到 stderr,正常輸出只走 stdout。 set -u for cmd in awk sort cut; do command -v "$cmd" >/dev/null 2>&1 || { echo "錯誤:環境缺 $cmd,無法合併發現。" >&2 exit 2 } done for f in "$@"; do case "$f" in -*) echo "用法:merge-findings.sh [發現檔…](不給檔名時讀 stdin)" >&2; exit 2 ;; esac [ -f "$f" ] || { echo "錯誤:找不到輸入檔 $f。請確認路徑後重試。" >&2; exit 2; } done TAB=$(printf '\t') TMP="${TMPDIR:-/tmp}/jsc-merge-findings.$$" trap 'rm -f "$TMP" "$TMP.sorted"' EXIT INT TERM # 第一欄先放排序用的嚴重度序位,排完再切掉。 awk -F"$TAB" ' BEGIN { OFS = "\t"; n = 0; bad = 0 } { sub(/\r$/, "", $0) if ($0 ~ /^[ \t]*$/) next if ($0 ~ /^#/) next if ($1 == "無發現") next where = (FILENAME == "" || FILENAME == "-") ? "stdin" : FILENAME if (NF != 6) { printf("錯誤:%s 第 %d 列有 %d 欄,應為 6 欄。\n", where, FNR, NF) > "/dev/stderr" bad = 1; next } if ($1 == "") { printf("錯誤:%s 第 %d 列的檔案路徑是空的。\n", where, FNR) > "/dev/stderr" bad = 1; next } if ($2 !~ /^[0-9]+$/ || $2 + 0 < 1) { printf("錯誤:%s 第 %d 列的行號「%s」不是正整數。\n", where, FNR, $2) > "/dev/stderr" bad = 1; next } if ($3 != "高" && $3 != "中" && $3 != "低") { printf("錯誤:%s 第 %d 列的嚴重度「%s」不在 高、中、低。\n", where, FNR, $3) > "/dev/stderr" bad = 1; next } rank = ($3 == "高") ? 1 : (($3 == "中") ? 2 : 3) key = $1 SUBSEP $2 if (!(key in seen)) { seen[key] = 1 order[++n] = key kfile[key] = $1; kline[key] = $2; krank[key] = rank kgroup[key] = $4; kitem[key] = $5; kdetail[key] = $6 } else { if (rank < krank[key]) krank[key] = rank kgroup[key] = kgroup[key] "|" $4 kitem[key] = kitem[key] "|" $5 kdetail[key] = kdetail[key] "|" $6 } } END { if (bad) exit 3 if (n == 0) exit 1 for (i = 1; i <= n; i++) { k = order[i] sev = (krank[k] == 1) ? "高" : ((krank[k] == 2) ? "中" : "低") print krank[k], kfile[k], kline[k], sev, kgroup[k], kitem[k], kdetail[k] } } ' "$@" > "$TMP" rc=$? [ "$rc" -eq 0 ] || exit "$rc" LC_ALL=C sort -t"$TAB" -k1,1n -k2,2 -k3,3n "$TMP" > "$TMP.sorted" || { echo "錯誤:排序失敗。" >&2 exit 2 } cut -f2- "$TMP.sorted" || { echo "錯誤:輸出失敗。" >&2 exit 2 } exit 0