การเว้นอักขระในพิมพ์ f


108

ฉันกำลังเขียนสคริปต์ bash เชลล์เพื่อแสดงว่ากระบวนการกำลังทำงานอยู่หรือไม่

จนถึงตอนนี้ฉันได้รับสิ่งนี้:

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

รหัสให้ผลลัพธ์นี้แก่ฉัน:

JBoss                                              [DOWN]

GlassFish                                          [UP]

verylongprocessname                                [UP]

ฉันต้องการเว้นช่องว่างระหว่างสองฟิลด์ด้วย '-' หรือ '*' เพื่อให้อ่านง่ายขึ้น ฉันจะทำได้อย่างไรโดยไม่รบกวนการจัดตำแหน่งของฟิลด์

ผลลัพธ์ที่ฉันต้องการคือ:

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

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

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

คำตอบ:


78

Pure 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' ... หน่อยได้ไหม
Édouard Lopez

3
@EdouardLopez: เครื่องหมายดอกจันแรกจะถูกแทนที่ด้วยศูนย์ในรายการอาร์กิวเมนต์ เครื่องหมายดอกจันที่สองจะถูกแทนที่ด้วยผลลัพธ์ของการคำนวณในอาร์กิวเมนต์ที่สอง ผลที่ตามมาสำหรับสตริง "AAAA" และ "bbbbb" '%0.31s'คือตัวอย่างหนึ่งที่ สตริง (อาร์กิวเมนต์สุดท้าย) จะถูกตัดให้สั้นลงตามความยาวที่ระบุไว้หลังจุด ศูนย์ป้องกันการเว้นช่องว่างจากการส่งออก ดังนั้นจึงมีการส่งออกยัติภังค์ 31 รายการ
หยุดชั่วคราวจนกว่าจะมีประกาศอีกครั้ง

1
หน้านี้สามารถช่วยให้เข้าใจคำตอบของ @Dennis Williamson: wiki.bash-hackers.org/commands/builtin/printf#modifiers
Édouard Lopez

{1..60} ต้องการ 60 เป็นตัวแปร ... เช่น "var = 60"
Reegan Miranda

@ReeganMiranda: วิธีการทำงานของเทคนิคนี้คือคุณฮาร์ดโค้ดค่าเป็นค่าที่ใหญ่ที่สุดที่คุณต้องการและใช้padlengthเพื่อเลือกความยาวที่แท้จริงในการส่งออก
หยุดชั่วคราวจนกว่าจะมีประกาศอีกครั้ง

70

ทุบตีบริสุทธิ์ ใช้ความยาวของค่า "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มีช่องว่างเว้นแต่จะมีการใช้ Escape อยู่แล้ว คุณจะได้รับหนึ่งบรรทัดพร้อมด้วยโทเค็นสองอันต่ออันจากนั้น [UP] สำหรับทุกๆโทเค็นที่คั่นด้วยช่องว่างสองรายการในตัวแปรของคุณจากนั้นบรรทัดเดียวที่ท้ายlineข้อความของคุณลบด้วยความยาวทั้งหมดของสตริงอินพุตของคุณ ดังนั้นโปรดระวังเนื่องจากอาจนำไปสู่จุดบกพร่องที่น่าสนใจและอาจไม่ปลอดภัยหากทำในสคริปต์ที่ซับซ้อน อย่างอื่นสั้นและเรียบง่าย :)
dodexahedron

19

วิธีแก้ปัญหาเล็กน้อย (แต่ใช้งานได้):

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

4
แต่บนเทอร์มินัลเท่านั้น หากเอาต์พุตถูกส่งไปยังไฟล์มันจะยุ่ง
thkala

5
แล้วสิ่งที่คุณคาดหวังจากวิธีแก้ปัญหาเล็กน้อย!? ทำงานเต็มรูปแบบด้วยการเปลี่ยนเส้นทางเอาต์พุต?!? ]: P
Nicola Leoni

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 ขีดกลาง
    ( ขีดกลางถูกตีความว่าเป็นตัวเลือกดังนั้นให้ใช้รหัส Escape ascii แทน)
  • "$PROC_NAME ..." - ต่อ $ PROC_NAME และขีดกลาง
  • | head -c 40 - ตัดแต่งสตริงเป็น 40 ตัวอักษรแรก

แปลกเมื่อฉันprintf 'x' {1..40}พิมพ์เพียงภาพเดียวxอืมมม
Krystian

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

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

เอาต์พุตไปยัง stdout:

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

4

โดยใช้echoเท่านั้น

anwser ของ @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

Simple Console 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 shell COLUMNS(เช่นความกว้างของเทอร์มินัลปัจจุบัน) ตัวแปรสภาพแวดล้อมจะต้องพร้อมใช้งานสำหรับสคริปต์ วิธีหนึ่งคือจัดหาตัวแปรสภาพแวดล้อมทั้งหมดโดยเรียกใช้สคริปต์ที่นำหน้าด้วย.(คำสั่ง "dot") ดังนี้:

. /path/to/script

หรือ (ดีกว่า) ส่งผ่านCOLUMNSตัวแปรอย่างชัดเจนเมื่อดำเนินการเช่นนี้:

/path/to/script $COLUMNS
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.