Прокладки символів у printf


107

Я пишу сценарій bash shell, щоб відобразити, чи працює процес чи ні.

Поки що я зрозумів це:

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='----------------------------------------'
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]

Магія - це $ {рядок: $ {# PROC_NAME}}, який використовує вилучення підстрокових башів, щоб почати повертатися лише з точки змінної рядка, яка повинна починатися з числа символів у PROC_NAME. tldp.org/LDP/abs/html/string-manipulation.html#SUBSTREXTR01
cwingrav

Зауважте, що це не обробляє той випадок, коли PROC_NAMEє пробіли, якщо вони вже не увійшли. Ви отримаєте один рядок з двома лексемами кожен, а потім [UP] для кожні два маркери, розділені пробілом у вашій змінній, а потім один рядок у кінці з lineтекстом мінус загальна довжина вхідного рядка. Тож будьте обережні, оскільки це може призвести до цікавих та потенційно небезпечних помилок, якщо це зробити у складному сценарії. Інакше короткий і простий. :)
додексаедр

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/@-/ /'
10.10

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

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

Вихід до stdout:

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

4

echoтільки з використанням

Прибічник @Dennis Williamson працює чудово, за винятком того, що я намагався це зробити за допомогою відлуння. Echo дозволяє виводити шаркатери з певним кольором. Використання printf призведе до видалення цього забарвлення та друку нечитабельних символів. Ось лише echoальтернатива:

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" використовується замість символу "-", щоб уникнути інтерпретації оболонки як командного прапора. Інший варіант - "3D", щоб використовувати "=".

За відсутності будь-якої довжини padle в якості аргументу код вище за замовчуванням до стандартної ширини терміналу 80 символів.

Щоб скористатися перевагами змінної оболонки bash COLUMNS(тобто шириною поточного терміналу), скрипт повинен бути доступний для змінної середовища. Один із способів - джерело всіх змінних середовища, виконавши сценарій, який передує .(команда "крапка"), наприклад:

. /path/to/script

або (краще) явно передайте COLUMNSзмінну під час виконання, наприклад:

/path/to/script $COLUMNS
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.