# bash `${#var}` 不是字节数 — locale 决定字符/字节语义

**Date**: 2026-05-15
**Tags**: bash, locale, utf-8, ai-review-feedback
**Related**: PR #380 / 沉淀 #373

## 症状

PR #380 第一次 commit 后 pre-push AI review 建议把 `PR_BODY_CHARS` 重命名为 `PR_BODY_BYTES`，理由是 "bash 的 `${#var}` 返回字节数，中文 UTF-8 = 3 bytes/字"。**采纳后 PR AI review 又指出这条建议本身就是错的**。

## 真相

bash 的 `${#var}` 与 `${var:0:N}` 实际**按当前 locale 解释**：

| locale | `${#var}` 语义 | `${var:0:N}` 语义 |
|---|---|---|
| `LANG=C` / `LANG=POSIX` | 字节数 | 按字节切（**可能切坏 UTF-8**）|
| `LANG=*.UTF-8`（CI 默认 + 多数 Linux）| 字符数 | 按字符切（不会切坏多字节序列）|

PR_BODY 截断时走**字符语义反而更安全**——`${var:0:N}` 永远不会切到 UTF-8 多字节的半道，产出合法 UTF-8。如果命名成 BYTES 反而要靠 `LC_ALL=C wc -c` + `head -c` 强制字节计数，而 `head -c` 在多字节中间切就会产出非法 UTF-8 喂给下游。

最终回到 `PR_BODY_LEN` + 注释明示 "UTF-8 locale 字符 / C locale 字节"。

## 元教训：AI review 反馈也要技术验证

这次踩坑链：

```
1. 我写 PR_BODY_CHARS（实际是字符语义，命名正确）
   ↓
2. pre-push AI review 建议改 BYTES（AI 错）
   ↓
3. 我采纳 → commit BYTES（我错——没做 locale 验证就采信 AI）
   ↓
4. PR 阶段 AI review 4 轮持续指出 BYTES 不准（AI 对了）
   ↓
5. 改回 LEN（最终对的命名）
```

**Lesson**: AI review 的 finding 是高信号但不是事实——尤其触及 shell quirks / locale / 边界 API 行为时，采纳前要做小验证（`echo $LANG; v="中"; echo ${#v}` 一行就能验证）。

## 副产品：#373 机制自验证

同 4 轮 review 把 3 条 suggestion 全部识别为"沿用上轮"而非每轮重新指控——这正是 #373 想解决的问题。**机制端到端在真实 PR 上跑通了**，state 去重 + PR body 注入两层正交工作。

## 验证

```bash
LANG=C   bash -c 'v="中"; echo ${#v}'   # → 3（字节）
LANG=en_US.UTF-8 bash -c 'v="中"; echo ${#v}'   # → 1（字符）
```
