Các ký tự đệm trong printf


107

Tôi đang viết một tập lệnh bash shell để hiển thị nếu một quá trình đang chạy hay không.

Cho đến nay, tôi đã nhận được điều này:

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

Mã cung cấp cho tôi kết quả này:

JBoss                                              [DOWN]

GlassFish                                          [UP]

verylongprocessname                                [UP]

Tôi muốn thêm khoảng cách giữa hai trường bằng dấu '-' hoặc '*' để dễ đọc hơn. Làm cách nào để làm điều đó mà không làm xáo trộn sự liên kết của các trường?

Đầu ra tôi muốn là:

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

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

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

Câu trả lời:


77

Pure Bash, không có tiện ích bên ngoài

Trình diễn này giải thích đầy đủ, nhưng bạn chỉ có thể bỏ trừ độ dài của chuỗi thứ hai nếu bạn muốn các dòng lệch-phải.

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

Thật không may, trong kỹ thuật đó, độ dài của chuỗi pad phải được mã hóa cứng để dài hơn chuỗi dài nhất mà bạn nghĩ bạn sẽ cần, nhưng độ dài của padlength có thể là một biến như hình minh họa. Tuy nhiên, bạn có thể thay thế dòng đầu tiên bằng ba dòng này để có thể sử dụng một biến cho độ dài của pad:

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

Vì vậy, pad ( padlimitpadlength) có thể dựa trên chiều rộng đầu cuối ( $COLUMNS) hoặc được tính toán từ độ dài của chuỗi dữ liệu dài nhất.

Đầu ra:

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

Không trừ độ dài của chuỗi thứ hai:

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

Dòng đầu tiên có thể là dòng tương đương (tương tự như sprintf):

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

hoặc tương tự cho kỹ thuật năng động hơn:

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

Bạn có thể in tất cả trên một dòng nếu muốn:

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

1
Bạn có thể giải thích một chút về phần printf '% *. * S' ... được không?
Édouard Lopez

3
@EdouardLopez: Dấu hoa thị đầu tiên được thay thế bằng số 0 trong danh sách đối số. Dấu hoa thị thứ hai được thay thế bằng kết quả của phép tính trong đối số thứ hai. Ví dụ: kết quả cho các chuỗi "aaaa" và "bbbbb" là '%0.31s'. Chuỗi (đối số cuối cùng) được cắt ngắn theo độ dài được chỉ định sau dấu chấm. Số 0 ngăn không cho bất kỳ khoảng đệm nào được xuất ra. Vì vậy, 31 dấu gạch nối là đầu ra.
Tạm dừng cho đến khi có thông báo mới.

1
Trang này có thể giúp hiểu câu trả lời của @Dennis
Édouard Lopez

{1..60} cần 60 dưới dạng biến; ... như "var = 60"
Reegan Miranda

@ReeganMiranda: Cách hoạt động của kỹ thuật này là bạn mã hóa giá trị thành giá trị lớn nhất mà bạn cần và sử dụng padlengthđể chọn độ dài thực tế để xuất.
Tạm dừng cho đến khi có thông báo mới.

68

Pure Bash. Sử dụng độ dài của giá trị 'PROC_NAME' làm phần bù cho chuỗi cố định 'dòng':

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

Điều này cho

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

Điều kỳ diệu là $ {line: $ {# PROC_NAME}}, sử dụng trích xuất chuỗi con bash để chỉ bắt đầu trả về từ một điểm của dòng biến, được đặt bắt đầu bằng số ký tự trong PROC_NAME. tldp.org/LDP/abs/html/string-manipulation.html#SUBSTREXTR01
cwingrav

Lưu ý rằng điều này không xử lý trường hợp PROC_NAMEcó khoảng trắng trừ khi chúng đã được thoát. Bạn sẽ nhận được một dòng với hai mã thông báo và sau đó [UP] cho mỗi hai mã thông báo được phân tách bằng dấu cách trong biến của bạn và sau đó là một dòng duy nhất ở cuối với linevăn bản của bạn trừ đi tổng độ dài của chuỗi đầu vào của bạn. Vì vậy, hãy cẩn thận, vì điều này có thể dẫn đến các lỗi thú vị và có khả năng không an toàn nếu được thực hiện trong một tập lệnh phức tạp. Nếu không thì ngắn gọn và đơn giản. :)
dodexahedron

19

Giải pháp tầm thường (nhưng hiệu quả):

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

4
Nhưng chỉ trên một thiết bị đầu cuối. Nếu đầu ra được gửi đến một tập tin, nó sẽ là một mớ hỗn độn.
thkala

5
vậy những gì bạn thực sự mong đợi từ một giải pháp tầm thường?!? làm việc đầy đủ cũng với chuyển hướng đầu ra?!? ]: P
Nicola Leoni

14

Tôi nghĩ đây là giải pháp đơn giản nhất. Nội trang vỏ thuần túy, không có toán học nội tuyến. Nó vay mượn từ các câu trả lời trước.

Chỉ là các chuỗi con và siêu biến $ {# ...}.

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"

Sản xuất

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

Sản xuất

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

12

Không có cách nào để độn bằng bất cứ thứ gì ngoài việc sử dụng khoảng trống printf. Bạn có thể sử dụng sed:

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

7
1 Có một vấn đề nếu PROC_NAME chứa một dấu gạch ngang - một cách dễ dàng giải quyết với một @ thêm: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]"

Giải trình:

  • printf '\055%.0s' {1..40} - Tạo 40 dấu gạch ngang
    (dấu gạch ngang được hiểu là tùy chọn vì vậy hãy sử dụng mã ascii thoát để thay thế)
  • "$PROC_NAME ..." - Nối $ PROC_NAME và dấu gạch ngang
  • | head -c 40 - Cắt chuỗi thành 40 ký tự đầu tiên

Lạ, khi tôi làm printf 'x' {1..40}nó chỉ in đơn xhmmm
Krystian

@Krystian đó là do bạn chưa sao chép định dạng: `` printf 'x% .0s' {1..40} `in 40 xgiây
artm

Để tránh dấu gạch ngang được hiểu là dấu gạch ngang kép tùy chọn có thể được sử dụng để báo hiệu rằng phần còn lại là các đối số không phải tùy chọnprintf -- "-%.0s" {1..40}
artm

7

Cái này thậm chí còn đơn giản hơn và không thực thi lệnh bên ngoài.

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

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

5

Đơn giản nhưng nó hoạt động:

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

Ví dụ về cách sử dụng:

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

Đầu ra cho stdout:

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

4

echochỉ sử dụng

Anwser của @Dennis Williamson đang hoạt động tốt ngoại trừ tôi đã cố gắng thực hiện việc này bằng cách sử dụng echo. Echo cho phép xuất ra các charcacters với một màu nhất định. Sử dụng printf sẽ loại bỏ màu đó và in các ký tự không đọc được. Đây là echo-chỉ thay thế:

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

đầu ra:

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

tất nhiên bạn có thể sử dụng tất cả các biến thể do @Dennis Williamson đề xuất cho dù bạn muốn phần bên phải được căn trái hay phải (thay thế 25 - ${#string1}bằng 25 - ${#string1} - ${#string2}v.v ...


2

Đây là một số khác:

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

Nếu bạn đang kết thúc các ký tự pad ở một số cột cố định, thì bạn có thể overpad và cutkéo dài:

# 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

Bảng điều khiển đơn giản Span / Fill / Pad / Padding với Phương pháp và Ví dụ về điều chỉnh tỷ lệ / thay đổi kích thước tự động.

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";
}

Thí dụ: create-console-spanner "loading graphics module" "[success]"

Bây giờ đây là một bộ-đầu-cuối-màu-ký-tự-đầy-đủ-tính-năng thực hiện mọi thứ liên quan đến việc in một chuỗi được định dạng màu và kiểu bằng cờ lê.

# 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";
}

Để in một màu , điều đó đủ đơn giản: paint-format "&red;This is %s\n" red Và bạn có thể muốn in đậm sau này:paint-format "&bold;%s!\n" WOW

Các -ltùy chọn vào paint-formatchức năng đo lường văn bản để bạn có thể làm số liệu console phông chữ hoạt động.

Các -vtùy chọn vào paint-formatchức năng hoạt động giống như printfnhưng không thể được cung cấp với-l

Bây giờ cho phần mở rộng !

paint-span "hello " . " &blue;world" [lưu ý: chúng tôi đã không thêm trình tự đầu cuối dòng mới, nhưng văn bản sẽ lấp đầy đầu cuối, vì vậy dòng tiếp theo chỉ có vẻ là chuỗi đầu cuối dòng mới]

và đầu ra của nó là:

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


0

Bash + seq để cho phép mở rộng tham số

Tương tự với câu trả lời @Dennis Williamson, nhưng nếu seqcó, độ dài của chuỗi pad không cần được mã hóa cứng. Đoạn mã sau cho phép chuyển một biến tới tập lệnh dưới dạng tham số vị trí:

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

Mã ASCII "2D" được sử dụng thay cho ký tự "-" để tránh trình bao hiểu nó như một cờ lệnh. Một tùy chọn khác là "3D" để sử dụng "=".

Trong trường hợp không có bất kỳ độ dài nào được truyền dưới dạng đối số, đoạn mã trên được đặt mặc định là chiều rộng đầu cuối chuẩn 80 ký tự.

Để tận dụng lợi thế của biến bash shell COLUMNS(tức là chiều rộng của terminal hiện tại), biến môi trường cần có sẵn cho script. Một cách là tạo nguồn tất cả các biến môi trường bằng cách thực thi tập lệnh đứng trước .(lệnh "dot"), như sau:

. /path/to/script

hoặc (tốt hơn) chuyển rõ ràng COLUMNSbiến khi thực thi, như thế này:

/path/to/script $COLUMNS
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.