Bash shell script output alignment

48,466

Solution 1

Use printf to format output (it's also more portable than echo). I would also store the real value of the colour escape sequences instead of storing them in a form that requires expansion by echo.

RED=$(tput setaf 1) GREEN=$(tput setaf 2) YELLOW=$(tput setaf 3)
NC=$(tput sgr0) 
online="${GREEN}online$NC" offline="${RED}offline$NC"

ping -c 1 "$hostname" > /dev/null 2>&1 && state=$online || state=$offline
printf 'Network %-15s: %s\n' "$hostname" "$state"

%-15s is a format specification that pads the strings with spaces on the right so as the length (in number of characters in zsh and fish and bytes in most other shells/printf) to be at least 15.

$ printf '|%-4s|\n' a ab abc abcd abcde
|a   |
|ab  |
|abc |
|abcd|
|abcde|
 printf '|%4s|\n' a ab abc abcd abcde
|   a|
|  ab|
| abc|
|abcd|
|abcde|

With truncation:

$ printf '|%.4s|\n' a ab abc abcd abcde
|a|
|ab|
|abc|
|abcd|
|abcd|
$ printf '|%4.4s|\n' a ab abc abcd abcde
|   a|
|  ab|
| abc|
|abcd|
|abcd|
$ printf '|%-4.4s|\n' a ab abc abcd abcde
|a   |
|ab  |
|abc |
|abcd|
|abcd|

Other utilities to format text in columns include POSIX expand:

printf 'Network %s\t: %s\n' "$hostname" "$state" | expand -t 30

(here expanding the TAB character (\t) with tab stops every 30 columns)

Or BSD column or POSIX pr:

printf 'Network %s\n: %s\n' "$hostname" "$state" | pr -at2

(here outputting on 2 36-column wide columns (see the -w option to change the page width from the default of 72)).

or BSD rs:

{
   while...
      printf 'Network %s\n: %s\n' "$hostname" "$state"
   done
} | rs -e 0 2

(like column won't start outputting until it has read all the input).

Or GNU columns:

printf 'Network %s\n: %s\n' "$hostname" "$state" | columns -w 25 -c 2

zsh also has some parameter expansion flags for string padding: ${(l:15:)hostname} for left padding and ${(r:15:)hostname} for right padding (with truncation). In prompt expansion (like in prompts or in print -P or as enabled in parameter expansions with the % flag), it also supports %F{green} for colour output, so you can do:

online='%F{green}online%f'
printf '%s\n' "Network ${(r:15:)hostname}: ${(%)online}"

Or:

print -rP "Network ${(r:15:)hostname}: $online"

Though the content of $hostname would then also be subject to prompt expansion, which would constitute a command injection vulnerability if the content of $hostname was not under your control (like in hostname='%<a[`reboot`]<')

Solution 2

Simply with column command:

yourscript.sh | column -t

The output:

Network  10.x.xx.xxx     :  Online
Network  10.x.xx.xxx     :  Offline
Network  10.x.xx.xxx     :  Offline
Network  10.x.xx.xxx     :  Offline
Network  10.x.xx.x       :  Online
Network  139.xxx.x.x     :  Online
Network  208.xx.xxx.xxx  :  Online
Network  193.xxx.xxx.x   :  Online

Solution 3

Update your script to insert a set number to \t (tabs) where you want to tab out to a column.

Output something simular to the following would give you the required alignment:

Network 10.x.xx.xxx\t: Online   
Network 10.x.xx.xxx\t: Offline   
Network 10.x.xx.xxx\t: Offline   
Network 10.x.xx.xxx\t: Offline   
Network 10.x.xx.x\t: Online   
Network 139.xxx.x.x\t: Online   
Network 208.xx.xxx.xxx\t: Online   
Network 193.xxx.xxx.x\t: Online

Solution 4

To display even better than @Roman

yourscript.sh | column -t -s $'\t'

Then add \t in each line to split it into column.

Share:
48,466

Related videos on Youtube

pijaaa
Author by

pijaaa

Updated on September 18, 2022

Comments

  • pijaaa
    pijaaa almost 2 years

    My script:

    date
    echo -e "${YELLOW}Network check${NC}\n\n"
    
    while read hostname
    do
    
    ping -c 1 "$hostname" > /dev/null 2>&1 &&
    
    echo -e "Network $hostname : ${GREEN}Online${NC}" ||
    echo -e "${GRAY}Network $hostname${NC} : ${RED}Offline${NC}"
    
    done < list.txt
            sleep 30
    clear
    done
    

    Is outputting info like this:

    Network 10.x.xx.xxx : Online   
    Network 10.x.xx.xxx : Offline   
    Network 10.x.xx.xxx : Offline   
    Network 10.x.xx.xxx : Offline   
    Network 10.x.xx.x : Online   
    Network 139.xxx.x.x : Online   
    Network 208.xx.xxx.xxx : Online   
    Network 193.xxx.xxx.x : Online
    

    which I'd like to clean up to get something like this:

    Network 10.x.xx.xxx       : Online  
    Network 10.x.xx.xxx       : Offline   
    Network 10.x.xx.xxx       : Offline    
    Network 10.x.xx.x         : Online    
    Network 139.xxx.x.x       : Online  
    Network 208.xx.xxx.xxx    : Online    
    Network 193.xxx.xxx.x     : Online  
    Network 193.xxx.xxx.xxx   : Offline
    
  • pijaaa
    pijaaa over 6 years
    im sorry for stupid question, but where i should put that command?
  • RomanPerekhrest
    RomanPerekhrest over 6 years
    @pijaaa, see my update, pipe with your script output
  • Stephen Kitt
    Stephen Kitt over 6 years
    It would be worth adding a short example based on the script in the question...
  • Stefan Wienströer
    Stefan Wienströer over 6 years
    @StephenKitt ty Stephen, I was trying to figure out how to make it more than just one line, and this honestly didn't occur to me!
  • Stéphane Chazelas
    Stéphane Chazelas over 6 years
    You'll notice that with the default tabstops every 8 columns, printf 'Network %s\t: Online\n' 8.8.8.8 192.168.122.123 doesn't align properly. You could work around that by using expand to expand the tabs with a different tab stop as shown in my answer.
  • Stéphane Chazelas
    Stéphane Chazelas over 6 years
    Note that column (a BSD command, also ported to Linux and found by default on some distributions, not to be confused with GNU columns) will need to read the whole input before it can start outputting something as it needs to compute the width of the columns based on the widest one.
  • Stefan Wienströer
    Stefan Wienströer over 6 years
    Please feel free to accept this as the answer then, by clicking the tick icon.