在printf中填充字符


107

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

到目前为止,我得到了:

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

代码给了我这个输出:

JBoss                                              [DOWN]

GlassFish                                          [UP]

verylongprocessname                                [UP]

我想用'-'或'*'填充两个字段之间的间隙,以使其更具可读性。在不干扰字段对齐的情况下该如何做?

我想要的输出是:

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

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

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

Answers:


77

纯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

不幸的是,在该技术中,必须将填充字符串的长度硬编码为比您认为所需的最长字符串更长,但是填充长度可以是如图所示的变量。但是,您可以将这三行替换为第一行,以便能够为垫的长度使用变量:

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

因此垫(padlimitpadlength)可以基于端子宽度($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"

1
您能解释一下printf'%*。* s'...部分吗?
爱德华·洛佩兹

3
@EdouardLopez:第一个星号由参数列表中的零代替。第二个星号由第二个参数中的计算结果代替。例如,对于字符串“ aaaa”和“ bbbbb”,结果为'%0.31s'。字符串(最后一个参数)被截断为点后指定的长度。零防止输出任何空格填充。因此输出31个连字符。
暂停,直到另行通知。

1
此页面可能有助于了解@Dennis Williamson答案:wiki.bash-hackers.org/commands/builtin/printf#modifiers
爱德华·洛佩兹(

{1..60}需要60作为变量; ...例如“ var = 60”
Reegan Miranda

@ReeganMiranda:此技术的工作方式是将值硬编码为所需的最大值,并用于padlength选择要输出的实际长度。
暂停,直到另行通知。

68

纯重击。使用“ 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子串提取仅从变量行的某个点开始返回,该行设置为以PROC_NAME中的字符数开头。tldp.org/LDP/abs/html/string-manipulation.html#SUBSTREXTR01
cwingrav '18年

请注意,PROC_NAME除非空格已被转义,否则无法处理有空格的情况。您将获得一行,每个行带有两个标记,然后在变量中每两个以空格分隔的标记为[UP],然后在一行的末尾加上line文本,减去输入字符串的总长度。因此请务必小心,因为如果在复杂的脚本中执行此操作,可能会导致有趣且可能不安全的错误。否则简短而简单。:)
dodexahedron

19

琐碎(但可行的)解决方案:

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

4
但仅在终端上。如果将输出发送到文件,那将是一团糟。
thkala 2010年

5
那么您对微不足道的解决方案的真正期望是什么?完全可以与输出重定向一起使用?!?]:P
Nicola Leoni 2010年

14

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

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

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

12

除了使用空格,没有其他方法可以填充printf。您可以使用sed

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

7
+1如果PROC_NAME包含破折号,则存在问题-可以通过附加@轻松解决:printf "%-50s@%s\n" ${PROC_NAME}@ [UP] | sed -e 's/ /-/g' -e 's/-@/ /' -e 's/@-/ /'
thkala 2010年

9
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}时候,它只打印单个xhmmm
Krystian

@Krystian是因为您尚未复制以下格式:`printf'x%.0s'{1..40}`打印40 xs
artm

为了避免将破折号解释为选项,可以使用双破折号来表示其余均为非选项参数printf -- "-%.0s" {1..40}
artm 19'Mar

7

这甚至更简单,并且不执行任何外部命令。

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

JBoss............... [UP]

5

简单但确实有效:

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]

4

echo仅使用

@Dennis Williamson的回答者工作得很好,除了我试图使用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}等代替...)


2

这是另一个:

$ { 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]

2

如果要在某个固定的列号处结束填充字符,则可以覆盖填充并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"

2

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

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]"

现在,这里是功能齐全的颜色字符终端套件,它可以完成有关用扳手打印颜色和样式格式的字符串的所有工作。

# 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 "&red;This is %s\n" red 然后您可能想在以后加粗:paint-format "&bold;%s!\n" WOW

该功能的-l选项将paint-format测量文本,以便您可以执行控制台字体指标操作。

-v选项paint-format功能与printf但不能提供-l

现在开始

paint-span "hello " . " &blue;world" [注意:我们没有添加换行符末尾序列,但是文本填充了终端符,因此下一行似乎只是换行符末尾序列]

的输出是:

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


0

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”以使用“ =”。

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

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

. /path/to/script

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

/path/to/script $COLUMNS
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.