diff --git a/README.md b/README.md index 831e5f6..79757fb 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,8 @@ sudo ./iptables-forward.sh - `✓`:该条规则对应的 PREROUTING / POSTROUTING / 双向 FORWARD 运行态规则都存在 - `!`:至少缺少一条运行态规则,或仅剩数据库记录 +列表表头与内容会按显示宽度对齐,中文与 ASCII 混排时也尽量保持列起始位置一致。 + ### 批处理模式 用于自动化测试或脚本调用: diff --git a/lib/common.sh b/lib/common.sh index 39f656a..6b2d7bc 100644 --- a/lib/common.sh +++ b/lib/common.sh @@ -57,18 +57,16 @@ log_ok() { _log_with_color "${CLR_GREEN}" OK "$@"; } display_width() { local input=${1-} - awk -v str="${input}" 'BEGIN { - n = split(str, chars, "") - width = 0 - for (i = 1; i <= n; i++) { - if (chars[i] ~ /[ -~]/) { - width += 1 - } else { - width += 2 - } - } - print width - }' + local char code width=0 + while IFS= read -r -n1 char; do + code=$(printf '%d' "'${char}") + if (( code >= 32 && code <= 126 )); then + ((width += 1)) + else + ((width += 2)) + fi + done < <(printf '%s' "${input}") + printf '%s\n' "${width}" } repeat_char() { diff --git a/lib/rules_mgr.sh b/lib/rules_mgr.sh index 1484549..9ff7db9 100644 --- a/lib/rules_mgr.sh +++ b/lib/rules_mgr.sh @@ -146,6 +146,12 @@ rule_health_mark() { fi } +render_rules_row() { + printf '%s %s %s %s %s %s %s %s\n' \ + "$(pad_right "${1-}" 3)" "$(pad_right "${2-}" 10)" "$(pad_right "${3-}" 10)" "$(pad_right "${4-}" 32)" \ + "$(pad_right "${5-}" 10)" "$(pad_right "${6-}" 12)" "$(pad_right "${7-}" 4)" "${8-}" +} + render_rules_plain() { local -a lines=("$@") local line idx uuid proto lport tip tport ipver desc health @@ -154,7 +160,7 @@ render_rules_plain() { return 0 fi - printf '%-3s %-10s %-10s %-32s %-10s %-12s %-4s %s\n' '#' '协议' '本地端口' '目标地址' '目标端口' 'IP版本' '状态' '描述' + render_rules_row '#' '协议' '本地端口' '目标地址' '目标端口' 'IP版本' '状态' '描述' for ((idx = 0; idx < ${#lines[@]}; idx++)); do line=${lines[idx]} uuid=$(rule_field "${line}" uuid) @@ -165,8 +171,7 @@ render_rules_plain() { ipver=$(rule_ipver_label "$(rule_field "${line}" ipver)") desc=$(rule_field "${line}" desc || true) health=$(rule_health_mark "${line}") - printf '%-3s %-10s %-10s %-32s %-10s %-12s %-4s %s\n' \ - "$((idx + 1))" "${proto}" "${lport}" "${tip}" "${tport}" "${ipver}" "${health}" "${desc}" + render_rules_row "$((idx + 1))" "${proto}" "${lport}" "${tip}" "${tport}" "${ipver}" "${health}" "${desc}" done } diff --git a/plan_iptables_forward.md b/plan_iptables_forward.md index df1318f..1147d99 100644 --- a/plan_iptables_forward.md +++ b/plan_iptables_forward.md @@ -364,7 +364,7 @@ persist_reload() { netfilter-persistent reload >/dev/null; } - `cmd_add` (tcp/ipv4) 后: - mock 日志包含 4 条命令 (PREROUTING/POSTROUTING/FORWARD 请求/回程) 且参数正确 - storage 中新增 1 条,UUID 与 mock 注释一致 -- `cmd_add` (both/both) 后:mock 日志包含 12 条命令 +- `cmd_add` (both/both) 后:mock 日志包含 16 条命令 - `cmd_delete` 后:mock 日志包含对应 `-D` 命令,storage 中记录被移除 - `cmd_add` 中途失败 (mock 注入错误):确认回滚,storage 不写入,已执行的 iptables `-A` 对应 `-D` 被调用 diff --git a/tests/test_common.sh b/tests/test_common.sh index 82221f5..9933607 100644 --- a/tests/test_common.sh +++ b/tests/test_common.sh @@ -26,6 +26,9 @@ assert_eq 'second' "$(cat "${prompt_tmp}")" 'prompt_input should consume subsequ assert_eq '' "${IPF_TEST_INPUTS}" 'prompt_input should drain queued test inputs in the current shell' unset IPF_TEST_INPUTS +assert_eq '4' "$(display_width '协议')" 'display_width should count CJK characters as double width' +assert_eq '8' "$(display_width 'IPv4规则')" 'display_width should handle ASCII and CJK mixed text' + assert_status 0 "$(status_of validate_ipv4 '0.0.0.0')" 'validate_ipv4 should accept 0.0.0.0' assert_status 0 "$(status_of validate_ipv4 '192.168.1.1')" 'validate_ipv4 should accept private address' assert_status 0 "$(status_of validate_ipv4 '255.255.255.255')" 'validate_ipv4 should accept broadcast' diff --git a/tests/test_rules_unit.sh b/tests/test_rules_unit.sh index cfa9e5f..d9a7715 100644 --- a/tests/test_rules_unit.sh +++ b/tests/test_rules_unit.sh @@ -89,6 +89,11 @@ del_count=$(grep -Ec ' -D ' "${IPTABLES_MOCK_LOG}") assert_eq '16' "${del_count}" 'deleting both/both rule should emit sixteen delete commands' assert_eq '0' "$(storage_count)" 'cmd_delete_uuid should remove rule from storage' +reset_mock_state +cmd_add_batch tcp 9080 127.0.0.1 98 4 'desc-mark' >/dev/null +mapfile -t list_lines < <(cmd_list 0) +assert_eq "$(display_width "${list_lines[0]%%描述*}")" "$(display_width "${list_lines[1]%%desc-mark*}")" 'cmd_list should align description column by display width' + reset_mock_state export IPTABLES_MOCK_FAIL_ON_N=2 assert_eq '1' "$(status_of cmd_add_batch tcp 9000 127.0.0.1 90 4 'rollback')" 'cmd_add_batch should fail when iptables mock injects an error'