Заполнение символов в printf


107

Я пишу сценарий оболочки bash, чтобы отображать, запущен процесс или нет.

Пока что получил вот что:

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

Код дает мне такой результат:

JBoss                                              [DOWN]

GlassFish                                          [UP]

verylongprocessname                                [UP]

Я хочу заполнить промежуток между двумя полями знаком «-» или «*», чтобы сделать его более читаемым. Как мне это сделать, не нарушая выравнивания полей?

Я хочу получить следующий результат:

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

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

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

Ответы:


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// /-}

Таким образом, блокнот ( 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"

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"
Риган Миранда

@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

Обратите внимание, что это не относится к случаю, когда PROC_NAMEесть пробелы, если они уже не экранированы. Вы получите одну строку с двумя токенами в каждой, а затем [ВВЕРХ] для каждых двух разделенных пробелами токенов в вашей переменной, а затем одну строку в конце с вашим lineтекстом за вычетом общей длины вашей входной строки. Так что будьте осторожны, поскольку это может привести к интересным и потенциально небезопасным ошибкам, если будет сделано в сложном сценарии. В остальном коротко и просто. :)
додексаэдр 05

19

Тривиальное (но рабочее) решение:

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

4
Но только на терминале. Если вывод отправить в файл, это будет беспорядок.
thkala

5
так чего же вы действительно ожидаете от тривиального решения?!? полная работа также с перенаправлением вывода?!? ]: P
Никола Леони

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

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}печатает только сингл xхммм
Кристиан

@Krystian это потому, что вы не скопировали формат: `printf 'x% .0s' {1..40}` печатает 40 xсек
artm

Чтобы избежать интерпретации тире как опции, можно использовать двойное тире, чтобы сигнализировать, что остальные не являются аргументамиprintf -- "-%.0s" {1..40}
artm 07

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только

Anwser @Dennis Williamson работает нормально, за исключением того, что я пытался сделать это с помощью 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}т. д.)


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

Простая консоль Span / Fill / Pad / Padding с автоматическим масштабированием / изменением размера Метод и пример.

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» используется вместо символа «-», чтобы оболочка не интерпретировала его как командный флаг. Другой вариант - «3D» использовать «=».

В отсутствие какой-либо длины поля, переданной в качестве аргумента, в приведенном выше коде по умолчанию используется стандартная ширина терминала 80 символов.

Чтобы воспользоваться преимуществами переменной оболочки bash COLUMNS(т. Е. Шириной текущего терминала), переменная среды должна быть доступна сценарию. Один из способов - получить все переменные среды, выполнив сценарий, которому предшествует .(команда "точка"), например:

. /path/to/script

или (лучше) явно передать COLUMNSпеременную при выполнении, например:

/path/to/script $COLUMNS
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.