可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I am writing a bash shell script to display if a process is running or not.
So far, I got this:
printf "%-50s %s\n" $PROC_NAME [UP]
The code gives me this output:
JBoss [DOWN]
GlassFish [UP]
verylongprocessname [UP]
I want to pad the gap between the two fields with a '-' or '*' to make it more readable. How do I do that without disturbing the alignment of the fields?
The output I want is:
JBoss ------------------------------------------- [DOWN]
GlassFish --------------------------------------- [UP]
verylongprocessname ----------------------------- [UP]
回答1:
Pure Bash, no external utilities
This demonstration does full justification, but you can just omit subtracting the length of the second string if you want ragged-right lines.
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
Unfortunately, in that technique, the length of the pad string has to be hardcoded to be longer than the longest one you think you'll need, but the padlength can be a variable as shown. However, you can replace the first line with these three to be able to use a variable for the length of the pad:
padlimit=60
pad=$(printf '%*s' "$padlimit")
pad=${pad// /-}
So the pad (padlimit
and padlength
) could be based on terminal width ($COLUMNS
) or computed from the length of the longest data string.
Output:
a--------------------------------bbbbbbb
aa--------------------------------bbbbbb
aaaa-------------------------------bbbbb
aaaaaaaa----------------------------bbbb
Without subtracting the length of the second string:
a---------------------------------------bbbbbbb
aa--------------------------------------bbbbbb
aaaa------------------------------------bbbbb
aaaaaaaa--------------------------------bbbb
The first line could instead be the equivalent (similar to sprintf
):
printf -v pad '%0.1s' "-"{1..60}
or similarly for the more dynamic technique:
printf -v pad '%*s' "$padlimit"
You can do the printing all on one line if you prefer:
printf '%s%*.*s%s\n' "$string1" 0 $((padlength - ${#string1} - ${#string2} )) "$pad" "$string2"
回答2:
Pure Bash. Use the length of the value of 'PROC_NAME' as offset for the fixed string '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}}"
This gives
abc ------------------------------------- [UP]
abcdef ---------------------------------- [UP]
回答3:
Trivial (but working) solution:
echo -e "---------------------------- [UP]\r$PROC_NAME "
回答4:
I think this is the simplest solution. Pure shell builtins, no inline math. It borrows from previous answers.
Just substrings and the ${#...} meta-variable.
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"
Produces
[>----- 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"
Produces
-----<] A very long header
---------------<] shrt hdr
回答5:
There's no way to pad with anything but spaces using printf
. You can use sed
:
printf "%-50s@%s\n" $PROC_NAME [UP] | sed -e 's/ /-/g' -e 's/@/ /' -e 's/-/ /'
回答6:
echo -n "$PROC_NAME $(printf '\055%.0s' {1..40})" | head -c 40 ; echo -n " [UP]"
Explanation:
printf '\055%.0s' {1..40}
- Create 40 dashes
(dash is interpreted as option so use escaped ascii code instead)
"$PROC_NAME ..."
- Concatenate $PROC_NAME and dashes
| head -c 40
- Trim string to first 40 chars
回答7:
This one is even simpler and execs no external commands.
$ PROC_NAME="JBoss"
$ PROC_STATUS="UP"
$ printf "%-.20s [%s]\n" "${PROC_NAME}................................" "$PROC_STATUS"
JBoss............... [UP]
回答8:
using echo
only
The anwser of @Dennis Williamson is working just fine except I was trying to do this using echo. Echo allows to output charcacters with a certain color. Using printf would remove that coloring and print unreadable characters. Here's the echo
-only alternative:
string1=abc
string2=123456
echo -en "$string1 "
for ((i=0; i< (25 - ${#string1}); i++)){ echo -n "-"; }
echo -e " $string2"
output:
abc ---------------------- 123456
of course you can use all the variations proposed by @Dennis Williamson whether you want the right part to be left- or right-aligned (replacing 25 - ${#string1}
by 25 - ${#string1} - ${#string2}
etc...
回答9:
Here's another one:
$ { 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]
回答10:
If you are ending the pad characters at some fixed column number, then you can overpad and cut
to length:
# 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"
回答11:
Simple Console Span/Fill/Pad/Padding with automatic scaling/resizing Method and Example.
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";
}
Example: create-console-spanner "loading graphics module" "[success]"
Now here is a full-featured-color-character-terminal-suite that does everything in regards to printing a color and style formatted string with a spanner.
# 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";
}
To print a color, that's simple enough: paint-format "&red;This is %s\n" red
And you might want to get bold later on: paint-format "&bold;%s!\n" WOW
The -l
option to the paint-format
function measures the text so you can do console font metrics operations.
The -v
option to the paint-format
function works the same as printf
but cannot be supplied with -l
Now for the spanning!
paint-span "hello " . " &blue;world"
[note: we didn't add newline terminal sequence, but the text fills the terminal, so the next line only appears to be a newline terminal sequence]
and the output of that is:
hello ............................. world
回答12:
Bash + seq to allow parameter expansion
Similar to @Dennis Williamson answer, but if seq
is available, the length of the pad string need not be hardcoded. The following code allows for passing a variable to the script as a positional parameter:
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
The ASCII code "2D" is used instead of the character "-" to avoid the shell interpreting it as a command flag. Another option is "3D" to use "=".
In absence of any padlength passed as an argument, the code above defaults to the 80 character standard terminal width.
To take advantage of the the bash shell variable COLUMNS
(i.e., the width of the current terminal), the environment variable would need to be available to the script. One way is to source all the environment variables by executing the script preceded by .
("dot" command), like this:
. /path/to/script
or (better) explicitly pass the COLUMNS
variable when executing, like this:
/path/to/script $COLUMNS