You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Bash中基于关联数组生成动态标记表格的简化方案问询

问题描述

现有一个Bash关联数组输入:

declare -A componentMatrix=(
  ["Component 1"    ]='debug,sysinstall,install,configure,deploy'
  ["Component 2"    ]='install,configure,smoketest,sysuninstall'
  ["Component 1234" ]='install,debug,dummy,showlogs'
  ["Component 32"   ]='install,sysuninstall,showlogs'
  ["Component 666"  ]='sysinstall,configure,showlogs'
)

需要生成自动计算列宽、星号居中且支持水平偏移的动态表格:

| showlogs     | dummy        | debug        | smoketest    | install      | configure    | deploy       | sysinstall   | sysuninstall |
-----------------+--------------+--------------+--------------+--------------+--------------+--------------+--------------+--------------+--------------+
Component 32     |       *      |              |              |              |       *      |              |              |              |       *      |
-----------------+--------------+--------------+--------------+--------------+--------------+--------------+--------------+--------------+--------------+
Component 2      |              |              |              |       *      |       *      |       *      |              |              |       *      |
-----------------+--------------+--------------+--------------+--------------+--------------+--------------+--------------+--------------+--------------+
Component 1      |              |              |       *      |              |       *      |       *      |       *      |       *      |              |
-----------------+--------------+--------------+--------------+--------------+--------------+--------------+--------------+--------------+--------------+
Component 1234   |       *      |       *      |       *      |              |       *      |              |              |              |              |
-----------------+--------------+--------------+--------------+--------------+--------------+--------------+--------------+--------------+--------------+
Component 666    |       *      |              |              |              |       *      |       *      |              |       *      |              |
-----------------+--------------+--------------+--------------+--------------+--------------+--------------+--------------+--------------+--------------+

当前实现代码较为复杂,希望得到更简洁的Bash写法、无需依赖Python/Perl的独立工具,以及可简化代码的Bash特性。

现有复杂实现代码:

#!/usr/bin/env bash

declare -A componentMatrix=(
  ["Component 1"    ]='debug,sysinstall,install,configure,deploy'
  ["Component 2"    ]='install,configure,smoketest,sysuninstall'
  ["Component 1234" ]='install,debug,dummy,showlogs'
  ["Component 32"   ]='install,sysuninstall,showlogs'
  ["Component 666"  ]='sysinstall,configure,showlogs'
)

show_component_table() {
  declare -A unique_cmds          # 记录唯一命令的关联数组
  local offset=${1:?}             # 表格水平偏移量变量
  local max_component_length=0    # 组件名称最大长度
  local max_cmd_length=0          # 命令最大长度
  local horizontal_line_char="-"  # 水平线字符
  local vertical_char="|"         # 垂直线字符
  local intersection_char="+"     # 交叉点字符
  local asterisk_char="*"         # 命令存在标记字符
  local component cmds cmd

  generate_horizontal_line() {
    printf "%*s" $offset
    for ((i=0; i<${first_col_width}; i++)); do
      printf "$horizontal_line_char"
    done
    printf "$intersection_char"
    for ((i=0; i<${#cmdlist[@]}; i++)); do
      for ((j=0; j<$generic_col_width; j++)); do
        printf "$horizontal_line_char"
      done
      printf "$intersection_char"
    done
    echo ""
  }

  for component in "${!componentMatrix[@]}"; do
    if (( ${#component} > max_component_length )); then
      max_component_length=${#component}
    fi
    cmds=${componentMatrix[$component]}
    for cmd in ${cmds//,/ }; do
      unique_cmds[$cmd]=1
    done
  done

  local cmdlist=("${!unique_cmds[@]}")

  for cmd in "${cmdlist[@]}"; do
    if (( ${#cmd} > max_cmd_length )); then
      max_cmd_length=${#cmd}
    fi
  done

  local generic_col_width=$((max_cmd_length + 2))
  local first_col_width=$((max_component_length + 2))
  local mid_component=$(((generic_col_width) / 2))
  local rest_component=$((generic_col_width - mid_component))

  printf "%*s" $offset
  printf "%-${first_col_width}s|" ""
  for cmd in "${cmdlist[@]}"; do
    printf " %-${max_cmd_length}s ${vertical_char}" "$cmd"
  done
  echo ""

  generate_horizontal_line

  for component in "${!componentMatrix[@]}"; do
    printf "%*s" $offset
    printf "%-${first_col_width}s|" "$component"
    cmds=${componentMatrix[$component]}
    for cmd in "${cmdlist[@]}"; do
      if [[ $cmds =~ $cmd ]]; then
        printf " %${mid_component}s%${rest_component}s" "$asterisk_char" "${vertical_char}"
      else
        printf "%-${generic_col_width}s%s" "" "${vertical_char}"
      fi
    done
    echo ""

    generate_horizontal_line
  done
}

show_component_table 10

优化方案

一、更简洁的Bash实现

通过简化循环逻辑、利用printf的重复输出特性、优化字符串匹配,可大幅精简代码:

#!/usr/bin/env bash

declare -A componentMatrix=(
  ["Component 1"]='debug,sysinstall,install,configure,deploy'
  ["Component 2"]='install,configure,smoketest,sysuninstall'
  ["Component 1234"]='install,debug,dummy,showlogs'
  ["Component 32"]='install,sysuninstall,showlogs'
  ["Component 666"]='sysinstall,configure,showlogs'
)

show_component_table() {
  local offset=${1:?}
  local -A cmd_map=()
  local max_comp_len=0 max_cmd_len=0

  # 收集命令并计算最大长度
  for comp in "${!componentMatrix[@]}"; do
    (( ${#comp} > max_comp_len )) && max_comp_len=${#comp}
    IFS=',' read -ra cmds <<< "${componentMatrix[$comp]}"
    for cmd in "${cmds[@]}"; do
      cmd_map["$cmd"]=1
      (( ${#cmd} > max_cmd_len )) && max_cmd_len=${#cmd}
    done
  done

  local -a cmd_list=("${!cmd_map[@]}")
  local col_width=$((max_cmd_len + 2))
  local first_col=$((max_comp_len + 2))
  # 预先生成水平线模板
  local hline=$(printf "%${first_col}s" | tr ' ' '-')
  hline+=$(printf "%s" "${cmd_list[@]/#/+$(printf "%${col_width}s" | tr ' ' '-')}")

  # 打印表头
  printf "%*s" "$offset" ""
  printf "%-${first_col}s|" ""
  for cmd in "${cmd_list[@]}"; do
    printf " %-${max_cmd_len}s |" "$cmd"
  done
  echo -e "\n%*s%s" "$offset" "" "$hline"

  # 打印组件数据行
  for comp in "${!componentMatrix[@]}"; do
    printf "%*s" "$offset" ""
    printf "%-${first_col}s|" "$comp"
    IFS=',' read -ra cmds <<< "${componentMatrix[$comp]}"
    local -A comp_cmds=()
    for cmd in "${cmds[@]}"; do comp_cmds["$cmd"]=1; done

    for cmd in "${cmd_list[@]}"; do
      if [[ -n ${comp_cmds["$cmd"]} ]]; then
        printf " %$((col_width/2))s%$((col_width - col_width/2))s" "*" "|"
      else
        printf "%-${col_width}s|" ""
      fi
    done
    echo -e "\n%*s%s" "$offset" "" "$hline"
  done
}

show_component_table 10

精简点说明:

  • 用IFS=',' read -ra安全拆分命令字符串,避免全局替换的潜在问题
  • 用printf + tr生成重复横线,替代嵌套循环
  • 预先生成完整水平线,避免重复计算
  • 用组件专属关联数组存储命令,提升匹配效率

二、无需依赖外部脚本语言的独立工具

利用系统自带的column命令,结合Bash预处理生成TSV(制表符分隔值)数据,可快速生成整齐表格:

#!/usr/bin/env bash

declare -A componentMatrix=(
  ["Component 1"]='debug,sysinstall,install,configure,deploy'
  ["Component 2"]='install,configure,smoketest,sysuninstall'
  ["Component 1234"]='install,debug,dummy,showlogs'
  ["Component 32"]='install,sysuninstall,showlogs'
  ["Component 666"]='sysinstall,configure,showlogs'
)

generate_tsv() {
  local -A cmd_map=()
  # 收集所有唯一命令
  for comp in "${!componentMatrix[@]}"; do
    IFS=',' read -ra cmds <<< "${componentMatrix[$comp]}"
    for cmd in "${cmds[@]}"; do cmd_map["$cmd"]=1; done
  done
  local -a cmd_list=("${!cmd_map[@]}")

  # 打印表头
  echo -e "Component\t${cmd_list[*]/%/\t}" | tr ' ' '\t'
  # 打印数据行
  for comp in "${!componentMatrix[@]}"; do
    local line="$comp"
    IFS=',' read -ra cmds <<< "${componentMatrix[$comp]}"
    local -A comp_cmds=()
    for cmd in "${cmds[@]}"; do comp_cmds["$cmd"]=1; done
    for cmd in "${cmd_list[@]}"; do
      line+=$'\t'"${comp_cmds[$cmd]:- }"
    done
    echo -e "${line// /\*}"
  done
}

# 生成TSV后用column格式化,添加水平偏移
generate_tsv | column -t -s $'\t' | sed "s/^/$(printf "%10s" "")/"

column是Linux发行版默认自带的工具,擅长文本对齐,配合TSV数据可自动处理列宽,无需手动计算。

三、可简化代码的Bash特性

  • 关联数组:用于快速去重和存在性检查,比如cmd_map存储所有唯一命令,comp_cmds存储当前组件的命令集合
  • printf高级格式化:利用%*s动态指定宽度、tr生成重复字符,替代循环输出
  • IFS拆分字符串:IFS=',' read -ra安全拆分逗号分隔的命令列表,避免全局替换的副作用
  • 参数扩展:比如${cmd_list[@]/#/+...}批量给数组元素添加前缀,快速构建水平线
  • 局部变量:用local限定变量作用域,避免全局污染,提升代码可读性

内容的提问来源于stack exchange,提问作者ikaerom

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 19:03:10