【问题标题】:Padding characters in printfprintf 中的填充字符
【发布时间】:2010-12-10 13:44:37
【问题描述】:

我正在编写一个 bash shell 脚本来显示进程是否正在运行。

到目前为止,我得到了这个:

printf "%-50s %s\n" $PROC_NAME [UP]

代码给了我这个输出:

JBoss                                              [DOWN]

GlassFish                                          [UP]

verylongprocessname                                [UP]

我想用 '-' 或 '*' 填充两个字段之间的空白,以使其更具可读性。如何在不影响字段对齐的情况下做到这一点?

我想要的输出是:

JBoss -------------------------------------------  [DOWN]

GlassFish ---------------------------------------  [UP]

verylongprocessname -----------------------------  [UP]

【问题讨论】:

    标签: bash shell printf


    【解决方案1】:

    纯 Bash,无外部实用程序

    这个演示完全证明了,但如果你想要右不规则的线条,你可以省略减去第二个字符串的长度。

    pad=$(printf '%0.1s' "-"{1..60})
    padlength=40
    string2='bbbbbbb'
    for string1 in a aa aaaa aaaaaaaa
    do
         printf '%s' "$string1"
         printf '%*.*s' 0 $((padlength - ${#string1} - ${#string2} )) "$pad"
         printf '%s\n' "$string2"
         string2=${string2:1}
    done
    

    不幸的是,使用这种技术,填充字符串的长度必须被硬编码为比您认为需要的最长长度更长,但padlength 可以是一个变量,如图所示。但是,您可以将第一行替换为这三个,以便能够使用一个变量来表示焊盘的长度:

    padlimit=60
    pad=$(printf '%*s' "$padlimit")
    pad=${pad// /-}
    

    因此填充(padlimit 和 padlength)可以基于终端宽度($COLUMNS)或根据最长数据字符串的长度计算。

    输出:

    a--------------------------------bbbbbbb
    aa--------------------------------bbbbbb
    aaaa-------------------------------bbbbb
    aaaaaaaa----------------------------bbbb
    

    不减去第二个字符串的长度:

    a---------------------------------------bbbbbbb
    aa--------------------------------------bbbbbb
    aaaa------------------------------------bbbbb
    aaaaaaaa--------------------------------bbbb
    

    第一行可以是等价的(类似于sprintf):

    printf -v pad '%0.1s' "-"{1..60}
    

    或者类似的更动态的技术:

    printf -v pad '%*s' "$padlimit"
    

    如果您愿意,您可以在一行上进行全部打印:

    printf '%s%*.*s%s\n' "$string1" 0 $((padlength - ${#string1} - ${#string2} )) "$pad" "$string2"
    

    【讨论】:

    • 你能解释一下 printf '%*.*s' ... 部分吗?
    • @EdouardLopez:第一个星号被参数列表中的零替换。第二个星号被第二个参数中的计算结果替换。例如,字符串“aaaa”和“bbbbb”的结果是'%0.31s'。字符串(最后一个参数)被截断为点后指定的长度。零可防止输出任何空格填充。所以输出了 31 个连字符。
    • 这个页面可以帮助理解@Dennis Williamson 回答:wiki.bash-hackers.org/commands/builtin/printf#modifiers
    • {1..60} 需要 60 作为变量;... 比如“var=60”
    • @ReeganMiranda:这种技术的工作方式是将值硬编码为所需的最大值,然后使用padlength 选择要输出的实际长度。
    【解决方案2】:

    纯重击。使用 'PROC_NAME' 值的长度作为固定字符串 'line' 的偏移量:

    line='----------------------------------------'
    PROC_NAME='abc'
    printf "%s %s [UP]\n" $PROC_NAME "${line:${#PROC_NAME}}"
    PROC_NAME='abcdef'
    printf "%s %s [UP]\n" $PROC_NAME "${line:${#PROC_NAME}}"
    

    这给了

    abc ------------------------------------- [UP]
    abcdef ---------------------------------- [UP]
    

    【讨论】:

    • 神奇的是${line:${#PROC_NAME}},它使用bash子串提取开始只从变量line的一个点返回,它被设置为从字符数开始在 PROC_NAME 中。 tldp.org/LDP/abs/html/string-manipulation.html#SUBSTREXTR01
    • 请注意,这不处理PROC_NAME 有空格的情况,除非它们已经被转义。您将得到一行,每行带有两个标记,然后为变量中的每两个空格分隔的标记 [UP],然后在末尾有一行,line 文本减去输入字符串的总长度。所以要小心,因为如果在复杂的脚本中完成,这可能会导致有趣且可能不安全的错误。否则简短而简单。 :)
    【解决方案3】:

    简单(但有效)的解决方案:

    echo -e "---------------------------- [UP]\r$PROC_NAME "
    

    【讨论】:

    • 但仅在终端上。如果将输出发送到文件中,那将是一团糟。
    【解决方案4】:

    我认为这是最简单的解决方案。纯 shell 内置函数,没有内联数学。它借鉴了以前的答案。

    只是子字符串和 ${#...} 元变量。

    A="[>---------------------<]";
    
    # Strip excess padding from the right
    #
    
    B="A very long header"; echo "${A:0:-${#B}} $B"
    B="shrt hdr"          ; echo "${A:0:-${#B}} $B"
    

    生产

    [>----- A very long header
    [>--------------- shrt hdr
    
    
    # Strip excess padding from the left
    #
    
    B="A very long header"; echo "${A:${#B}} $B"
    B="shrt hdr"          ; echo "${A:${#B}} $B"
    

    生产

    -----<] A very long header
    ---------------<] shrt hdr
    

    【讨论】:

      【解决方案5】:

      除了使用printf 的空格之外,没有其他方法可以填充。你可以使用sed:

      printf "%-50s@%s\n" $PROC_NAME [UP] | sed -e 's/ /-/g' -e 's/@/ /' -e 's/-/ /'
      

      【讨论】:

      • +1 如果 PROC_NAME 包含破折号,则会出现问题 - 可以通过额外的@轻松解决:printf "%-50s@%s\n" ${PROC_NAME}@ [UP] | sed -e 's/ /-/g' -e 's/-@/ /' -e 's/@-/ /'
      【解决方案6】:

      简单但确实有效:

      printf "%-50s%s\n" "$PROC_NAME~" "~[$STATUS]" | tr ' ~' '- '
      

      使用示例:

      while read PROC_NAME STATUS; do  
          printf "%-50s%s\n" "$PROC_NAME~" "~[$STATUS]" | tr ' ~' '- '
      done << EOT 
      JBoss DOWN
      GlassFish UP
      VeryLongProcessName UP
      EOT
      

      输出到标准输出:

      JBoss -------------------------------------------- [DOWN]
      GlassFish ---------------------------------------- [UP]
      VeryLongProcessName ------------------------------ [UP]
      

      【讨论】:

      • 就简单性而言,这是一个非常优雅的解决方案。我终于明白了tr 命令是如何在第一个字符串输出之后 实现空格的。 tr 翻译不是正则表达式模式匹配,而是 SET2 对应于 SET1。所以tr ' ~' '- ' 与tr '~ ' ' -' 相同:将任何和所有空格字符转换为破折号并将任何和所有波浪字符转换为空格 - 反之亦然。
      【解决方案7】:
      echo -n "$PROC_NAME $(printf '\055%.0s' {1..40})" | head -c 40 ; echo -n " [UP]"
      

      解释:

      • printf '\055%.0s' {1..40} - 创建 40 个破折号
        (破折号被解释为选项,因此请改用转义的 ascii 代码)
      • "$PROC_NAME ..." - 连接 $PROC_NAME 和破折号
      • | head -c 40 - 将字符串修剪为前 40 个字符

      【讨论】:

      • 奇怪,当我执行printf 'x' {1..40} 时,它只打印单个x hmmm
      • @Krystian 那是因为你没有复制格式:` printf 'x%.0s' {1..40}` 打印 40 xs
      • 为了避免破折号被解释为选项双破折号可以用来表示其余是非选项参数printf -- "-%.0s" {1..40}
      【解决方案8】:

      这个更简单,不执行任何外部命令。

      $ PROC_NAME="JBoss"
      $ PROC_STATUS="UP"
      $ printf "%-.20s [%s]\n" "${PROC_NAME}................................" "$PROC_STATUS"
      
      JBoss............... [UP]
      

      【讨论】:

        【解决方案9】:

        仅使用echo

        @Dennis Williamson 的 anwser 工作得很好,只是我试图使用 echo 来做到这一点。 Echo 允许输出具有某种颜色的字符。使用 printf 将删除该着色并打印不可读的字符。这是echo-only 的替代方案:

        string1=abc
        string2=123456
        echo -en "$string1 "
        for ((i=0; i< (25 - ${#string1}); i++)){ echo -n "-"; }
        echo -e " $string2"
        

        输出:

        abc ---------------------- 123456
        

        当然,您可以使用@Dennis Williamson 提出的所有变体,无论您希望右侧部分是左对齐还是右对齐(将25 - ${#string1} 替换为25 - ${#string1} - ${#string2} 等...

        【讨论】:

          【解决方案10】:

          这是另一个:

          $ { echo JBoss DOWN; echo GlassFish UP; } | while read PROC STATUS; do echo -n "$PROC "; printf "%$((48-${#PROC}))s " | tr ' ' -; echo " [$STATUS]"; done
          JBoss -------------------------------------------- [DOWN]
          GlassFish ---------------------------------------- [UP]
          

          【讨论】:

            【解决方案11】:

            如果您在某个固定列号处结束填充字符,那么您可以过度填充和cut 到长度:

            # Previously defined:
            # PROC_NAME
            # PROC_STATUS
            
            PAD="--------------------------------------------------"
            LINE=$(printf "%s %s" "$PROC_NAME" "$PAD" | cut -c 1-${#PAD})
            printf "%s %s\n" "$LINE" "$PROC_STATUS"
            

            【讨论】:

              【解决方案12】:

              具有自动缩放/调整大小的简单控制台跨度/填充/填充/填充方法和示例。

              function create-console-spanner() {
                  # 1: left-side-text, 2: right-side-text
                  local spanner="";
                  eval printf -v spanner \'"%0.1s"\' "-"{1..$[$(tput cols)- 2 - ${#1} - ${#2}]}
                  printf "%s %s %s" "$1" "$spanner" "$2";
              }
              

              示例: create-console-spanner "loading graphics module" "[success]"

              现在这里有一个 full-featured-color-character-terminal-suite,它可以用扳手打印颜色和样式格式的字符串。

              # Author: Triston J. Taylor <pc.wiz.tt@gmail.com>
              # Date: Friday, October 19th, 2018
              # License: OPEN-SOURCE/ANY (NO-PRODUCT-LIABILITY OR WARRANTIES)
              # Title: paint.sh
              # Description: color character terminal driver/controller/suite
              
              declare -A PAINT=([none]=`tput sgr0` [bold]=`tput bold` [black]=`tput setaf 0` [red]=`tput setaf 1` [green]=`tput setaf 2` [yellow]=`tput setaf 3` [blue]=`tput setaf 4` [magenta]=`tput setaf 5` [cyan]=`tput setaf 6` [white]=`tput setaf 7`);
              
              declare -i PAINT_ACTIVE=1;
              
              function paint-replace() {
                  local contents=$(cat)
                  echo "${contents//$1/$2}"
              }
              
              source <(cat <<EOF
              function paint-activate() {
                  echo "\$@" | $(for k in ${!PAINT[@]}; do echo -n paint-replace \"\&$k\;\" \"\${PAINT[$k]}\" \|; done) cat;
              }
              EOF
              )
              
              source <(cat <<EOF
              function paint-deactivate(){
                  echo "\$@" | $(for k in ${!PAINT[@]}; do echo -n paint-replace \"\&$k\;\" \"\" \|; done) cat;    
              }
              EOF
              )
              
              function paint-get-spanner() {
                  (( $# == 0 )) && set -- - 0;
                  declare -i l=$(( `tput cols` - ${2}))
                  eval printf \'"%0.1s"\' "${1:0:1}"{1..$l}
              }
              
              function paint-span() {
                  local left_format=$1 right_format=$3
                  local left_length=$(paint-format -l "$left_format") right_length=$(paint-format -l "$right_format")
                  paint-format "$left_format";
                  paint-get-spanner "$2" $(( left_length + right_length));
                  paint-format "$right_format";
              }
              
              function paint-format() {
                  local VAR="" OPTIONS='';
                  local -i MODE=0 PRINT_FILE=0 PRINT_VAR=1 PRINT_SIZE=2;
                  while [[ "${1:0:2}" =~ ^-[vl]$ ]]; do
                      if [[ "$1" == "-v" ]]; then OPTIONS=" -v $2"; MODE=$PRINT_VAR; shift 2; continue; fi;
                      if [[ "$1" == "-l" ]]; then OPTIONS=" -v VAR"; MODE=$PRINT_SIZE; shift 1; continue; fi;
                  done;
                  OPTIONS+=" --"
                  local format="$1"; shift;
                  if (( MODE != PRINT_SIZE && PAINT_ACTIVE )); then
                      format=$(paint-activate "$format&none;")
                  else
                      format=$(paint-deactivate "$format")
                  fi
                  printf $OPTIONS "${format}" "$@";
                  (( MODE == PRINT_SIZE )) && printf "%i\n" "${#VAR}" || true;
              }
              
              function paint-show-pallette() {
                  local -i PAINT_ACTIVE=1
                  paint-format "Normal: &red;red &green;green &blue;blue &magenta;magenta &yellow;yellow &cyan;cyan &white;white &black;black\n";
                  paint-format "  Bold: &bold;&red;red &green;green &blue;blue &magenta;magenta &yellow;yellow &cyan;cyan &white;white &black;black\n";
              }
              

              要打印颜色,很简单:paint-format "&amp;red;This is %s\n" red 以后你可能想加粗:paint-format "&amp;bold;%s!\n" WOW

              paint-format 函数的-l 选项测量文本,以便您可以进行控制台字体度量操作。

              paint-format 函数的-v 选项与printf 的作用相同,但不能与-l 一起提供

              现在是跨越!

              paint-span "hello " . " &amp;blue;world"[注意:我们没有添加换行符终端序列,而是文本填充了终端,所以下一行只显示为换行符终端序列]

              然后输出是:

              hello ............................. world

              【讨论】:

                【解决方案13】:

                Bash + seq 允许参数扩展

                类似于@Dennis Williamson 的回答,但如果seq 可用,则不需要对填充字符串的长度进行硬编码。以下代码允许将变量作为位置参数传递给脚本:

                COLUMNS="${COLUMNS:=80}"
                padlength="${1:-$COLUMNS}"
                pad=$(printf '\x2D%.0s' $(seq "$padlength") )
                
                string2='bbbbbbb'
                for string1 in a aa aaaa aaaaaaaa
                do
                     printf '%s' "$string1"
                     printf '%*.*s' 0 $(("$padlength" - "${#string1}" - "${#string2}" )) "$pad"
                     printf '%s\n' "$string2"
                     string2=${string2:1}
                done
                

                使用 ASCII 代码“2D”代替字符“-”以避免 shell 将其解释为命令标志。另一个选项是使用“=”的“3D”。

                在没有任何 padlength 作为参数传递的情况下,上面的代码默认为 80 个字符的标准终端宽度。

                要利用 bash shell 变量 COLUMNS(即当前终端的宽度),脚本需要使用环境变量。一种方法是通过执行前面带有.(“dot”命令)的脚本来获取所有环境变量,如下所示:

                . /path/to/script
                

                或(更好)在执行时显式传递COLUMNS 变量,如下所示:

                /path/to/script $COLUMNS
                

                【讨论】:

                  猜你喜欢
                  • 2010-12-16
                  • 1970-01-01
                  • 2012-04-17
                  • 2021-03-08
                  • 2020-02-25
                  • 2013-07-24
                  • 2011-05-07
                  • 2010-09-21
                  • 1970-01-01
                  相关资源
                  最近更新 更多