【问题标题】:How can I align the columns of tables in Bash?如何在 Bash 中对齐表格的列?
【发布时间】:2012-09-27 22:43:54
【问题描述】:

我想输出一个表格格式的文本。我试图做的是用 '\t' 回显数组的元素,但它没有对齐。

我的代码

for((i=0;i<array_size;i++));
do
   echo stringarray[$i] $'\t' numberarray[$i] $'\t' anotherfieldarray[$i]
done;

我的输出

a very long string..........     112232432      anotherfield
a smaller string         123124343     anotherfield

期望的输出

a very long string..........     112232432      anotherfield
a smaller string                 123124343      anotherfield

【问题讨论】:

    标签: bash echo


    【解决方案1】:

    使用 column 命令:

    column -t -s' ' filename
    

    【讨论】:

    • 这不适用于问题中给出的示例,因为第一列数据中有空格。
    • @BurhanAli 我需要重复我之前的评论吗?所有答案都假设有一些分隔符。 OP没有说分隔符。所以同样的分隔符也可以在列中使用。 因为第一列数据有空格那你怎么称呼它为第一列呢?
    • 无需重复。我读了他们。我的评论基于问题中所需的输出。在给定的输入上使用此答案不会产生所需的输出。
    • 准备分隔符的例子:cat /etc/fstab | sed -r 's/\s+/ /g' | column -t -s' '
    • 准备分隔符的例子:sed -r 's/\s+/ /g' /etc/fstab | column -t -s' '
    【解决方案2】:

    以下代码已经过测试,完全符合原始问题的要求。

    参数:

    %30s 30 个字符的列和文本右对齐。
    %10d 整数表示法,%10s 也可以。 \

    stringarray[0]="a very long string.........."
    # 28Char (max length for this column)
    numberarray[0]=1122324333
    # 10digits (max length for this column)
    anotherfield[0]="anotherfield"
    # 12Char (max length for this column)
    stringarray[1]="a smaller string....."
    numberarray[1]=123124343
    anotherfield[1]="anotherfield"
    
    printf "%30s %10d %13s" "${stringarray[0]}" ${numberarray[0]} "${anotherfield[0]}"
    printf "\n"
    printf "%30s %10d %13s" "${stringarray[1]}" ${numberarray[1]} "${anotherfield[1]}"
    # a var string with spaces has to be quoted
    printf "\n Next line will fail \n"
    printf "%30s %10d %13s" ${stringarray[0]} ${numberarray[0]} "${anotherfield[0]}"
    
    
    
      a very long string.......... 1122324333  anotherfield
             a smaller string.....  123124343  anotherfield
    

    【讨论】:

    • 正如上面@steffen 所指出的,左对齐使用“-”符号,即 printf "%-30s " "${stringarray[0]}"
    【解决方案3】:

    以防万一有人想在 PHP 中这样做,我在 GitHub 上发布了一个要点:

    https://gist.github.com/redestructa/2a7691e7f3ae69ec5161220c99e2d1b3

    只需调用:

    $output = $tablePrinter->printLinesIntoArray($items, ['title', 'chilProp2']);
    

    如果您使用的是早于 7.2 的 PHP 版本,您可能需要调整代码。

    之后,根据您的环境调用 echo 或 writeLine。

    【讨论】:

      【解决方案4】:

      这比你想象的要容易。

      如果您也在使用以分号分隔的文件和标题:

      $ (head -n1 file.csv && sort file.csv | grep -v <header>) | column -s";" -t
      

      如果您使用的是数组(使用制表符作为分隔符):

      for((i=0;i<array_size;i++));
      do
      
         echo stringarray[$i] $'\t' numberarray[$i] $'\t' anotherfieldarray[$i] >> tmp_file.csv
      
      done;
      
      cat file.csv | column -t
      

      【讨论】:

        【解决方案5】:

        我不确定你在哪里运行它,但你发布的代码不会产生你给出的输出,至少在我熟悉的 Bash 版本中不会。

        试试这个:

        stringarray=('test' 'some thing' 'very long long long string' 'blah')
        numberarray=(1 22 7777 8888888888)
        anotherfieldarray=('other' 'mixed' 456 'data')
        array_size=4
        
        for((i=0;i<array_size;i++))
        do
            echo ${stringarray[$i]} $'\x1d' ${numberarray[$i]} $'\x1d' ${anotherfieldarray[$i]}
        done | column -t -s$'\x1d'
        

        请注意,我使用的是group separator character (0x1D) 而不是制表符,因为如果您从文件中获取这些数组,它们可能包含制表符。

        【讨论】:

          【解决方案6】:

          要获得与您需要的完全相同的输出,您需要像这样格式化文件:

          a very long string..........\t     112232432\t     anotherfield\n
          a smaller string\t      123124343\t     anotherfield\n
          

          然后使用:

          $ column -t -s $'\t' FILE
          a very long string..........  112232432  anotherfield
          a smaller string              123124343  anotherfield
          

          【讨论】:

          【解决方案7】:

          awk处理标准输入的解决方案

          由于column 不是 POSIX,也许这是:

          mycolumn() (
            file="${1:--}"
            if [ "$file" = - ]; then
              file="$(mktemp)"
              cat > "${file}"
            fi
            awk '
            FNR == 1 { if (NR == FNR) next }
            NR == FNR {
              for (i = 1; i <= NF; i++) {
                l = length($i)
                if (w[i] < l)
                  w[i] = l
              }
              next
            }
            {
              for (i = 1; i <= NF; i++)
                printf "%*s", w[i] + (i > 1 ? 1 : 0), $i
              print ""
            }
            ' "$file" "$file"
            if [ "$1" = - ]; then
              rm "$file"
            fi
          )
          

          测试:

          printf '12 1234 1
          12345678 1 123
          1234 123456 123456
          ' > file
          

          测试命令:

          mycolumn file
          mycolumn <file
          mycolumn - <file
          

          所有人的输出:

                12   1234      1
          12345678      1    123
              1234 123456 123456
          

          另见:

          【讨论】:

          • 末尾的if [ "$file" = - ]; then 应该是if [ "$1" = - ]; then。使用当前代码,您永远不会清理临时文件。
          【解决方案8】:

          printf 很棒,但人们忘记了它。

          $ for num in 1 10 100 1000 10000 100000 1000000; do printf "%10s %s\n" $num "foobar"; done
                   1 foobar
                  10 foobar
                 100 foobar
                1000 foobar
               10000 foobar
              100000 foobar
             1000000 foobar
          
          $ for((i=0;i<array_size;i++));
          do
              printf "%10s %10d %10s" stringarray[$i] numberarray[$i] anotherfieldarray[%i]
          done
          

          请注意,我使用 %10s 表示字符串。 %s 是重要的部分。它告诉它使用一个字符串。中间的10 表示它是多少列。 %d 用于数字(数字)。

          man 1 printf 了解更多信息。

          【讨论】:

          • 只有一个在打印表格时有用的建议:%-10s 将生成长度为 10 的左对齐字符串
          • @UtahJarhead 对变量 stringarray[$i] 的引用应替换为 ${stringarray[i]} 并具有必须引用的第一个字符串空格 "${stringarray[i]}"避免空格字符被解释为分隔符。
          【解决方案9】:
          function printTable()
          {
              local -r delimiter="${1}"
              local -r data="$(removeEmptyLines "${2}")"
          
              if [[ "${delimiter}" != '' && "$(isEmptyString "${data}")" = 'false' ]]
              then
                  local -r numberOfLines="$(wc -l <<< "${data}")"
          
                  if [[ "${numberOfLines}" -gt '0' ]]
                  then
                      local table=''
                      local i=1
          
                      for ((i = 1; i <= "${numberOfLines}"; i = i + 1))
                      do
                          local line=''
                          line="$(sed "${i}q;d" <<< "${data}")"
          
                          local numberOfColumns='0'
                          numberOfColumns="$(awk -F "${delimiter}" '{print NF}' <<< "${line}")"
          
                          # Add Line Delimiter
          
                          if [[ "${i}" -eq '1' ]]
                          then
                              table="${table}$(printf '%s#+' "$(repeatString '#+' "${numberOfColumns}")")"
                          fi
          
                          # Add Header Or Body
          
                          table="${table}\n"
          
                          local j=1
          
                          for ((j = 1; j <= "${numberOfColumns}"; j = j + 1))
                          do
                              table="${table}$(printf '#| %s' "$(cut -d "${delimiter}" -f "${j}" <<< "${line}")")"
                          done
          
                          table="${table}#|\n"
          
                          # Add Line Delimiter
          
                          if [[ "${i}" -eq '1' ]] || [[ "${numberOfLines}" -gt '1' && "${i}" -eq "${numberOfLines}" ]]
                          then
                              table="${table}$(printf '%s#+' "$(repeatString '#+' "${numberOfColumns}")")"
                          fi
                      done
          
                      if [[ "$(isEmptyString "${table}")" = 'false' ]]
                      then
                          echo -e "${table}" | column -s '#' -t | awk '/^\+/{gsub(" ", "-", $0)}1'
                      fi
                  fi
              fi
          }
          
          function removeEmptyLines()
          {
              local -r content="${1}"
          
              echo -e "${content}" | sed '/^\s*$/d'
          }
          
          function repeatString()
          {
              local -r string="${1}"
              local -r numberToRepeat="${2}"
          
              if [[ "${string}" != '' && "${numberToRepeat}" =~ ^[1-9][0-9]*$ ]]
              then
                  local -r result="$(printf "%${numberToRepeat}s")"
                  echo -e "${result// /${string}}"
              fi
          }
          
          function isEmptyString()
          {
              local -r string="${1}"
          
              if [[ "$(trimString "${string}")" = '' ]]
              then
                  echo 'true' && return 0
              fi
          
              echo 'false' && return 1
          }
          
          function trimString()
          {
              local -r string="${1}"
          
              sed 's,^[[:blank:]]*,,' <<< "${string}" | sed 's,[[:blank:]]*$,,'
          }
          

          样本运行

          $ cat data-1.txt
          HEADER 1,HEADER 2,HEADER 3
          
          $ printTable ',' "$(cat data-1.txt)"
          +-----------+-----------+-----------+
          | HEADER 1  | HEADER 2  | HEADER 3  |
          +-----------+-----------+-----------+
          
          $ cat data-2.txt
          HEADER 1,HEADER 2,HEADER 3
          data 1,data 2,data 3
          
          $ printTable ',' "$(cat data-2.txt)"
          +-----------+-----------+-----------+
          | HEADER 1  | HEADER 2  | HEADER 3  |
          +-----------+-----------+-----------+
          | data 1    | data 2    | data 3    |
          +-----------+-----------+-----------+
          
          $ cat data-3.txt
          HEADER 1,HEADER 2,HEADER 3
          data 1,data 2,data 3
          data 4,data 5,data 6
          
          $ printTable ',' "$(cat data-3.txt)"
          +-----------+-----------+-----------+
          | HEADER 1  | HEADER 2  | HEADER 3  |
          +-----------+-----------+-----------+
          | data 1    | data 2    | data 3    |
          | data 4    | data 5    | data 6    |
          +-----------+-----------+-----------+
          
          $ cat data-4.txt
          HEADER
          data
          
          $ printTable ',' "$(cat data-4.txt)"
          +---------+
          | HEADER  |
          +---------+
          | data    |
          +---------+
          
          $ cat data-5.txt
          HEADER
          
          data 1
          
          data 2
          
          $ printTable ',' "$(cat data-5.txt)"
          +---------+
          | HEADER  |
          +---------+
          | data 1  |
          | data 2  |
          +---------+
          

          参考库:https://github.com/gdbtek/linux-cookbooks/blob/master/libraries/util.bash

          【讨论】:

          • 谢谢!这正是我想要的。对于 mac 用户:您必须删除 echo 命令中的 -e参数才能正确打印破折号。
          • 很棒的样品!谢谢!
          • 更改输出颜色确实会影响对齐。不知道为什么......嗯
          • @mattdevio 你找到颜色的修复了吗?
          • 注意!参考链接有更新的功能。
          猜你喜欢
          • 2012-03-12
          • 2017-01-23
          • 1970-01-01
          • 2012-11-02
          • 1970-01-01
          • 2014-12-11
          • 2018-04-03
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多