For a full list of BASHing data blog posts see the index page.     RSS

ASCII score bars and a gorblimey command

In January 2021, Stack Overflow contributor "1511" was having trouble getting the desired result in BASH:

I have a file with a bunch of integers like this:


The goal is to convert those integers in stats like in a videogame, for example, if the number is 6, the stats must be ******----, if the number is 4 the result must be ****------.

Stack Overflow contributors proposed various solutions. I'll demonstrate two of them and then suggest a third.

from anubhava:

awk '{s = sprintf("%*s", $1, ""); \
gsub(/ /, "*", s); \
p = sprintf("%*s", 10-$1, ""); \
gsub(/ /, "-", p); \
print s p}' file


This AWK command finishes by printing 2 concatenated items, "s" and "p" (print s p).
"s" is first built by sprintf, which prints the number of blank spaces equal to the number in the first (and only) input field (sprintf("%*s", $1, "")). For example, when processing the first line of "file", "s" will first be 6 blank spaces (and no newline).
gsub is then used to convert each blank space in "s" to an asterisk (gsub(/ /, "*", s)).
A similar process is used to generate a string of hyphens "p", where the number of hyphens is 10 less the number in the input field (p = sprintf("%*s", 10-$1, ""); gsub(/ /, "-", p)). For the first line in "file", the string will have 4 hyphens.
Finally, print concatenates the asterisk string and the hyphen string; for the first line in "file", "******----" is printed.

from Ed Morton:

awk 'BEGIN {lgth = 10; \
curr = base = sprintf("%*s",lgth,""); \
gsub(/ /,"*",curr); \
gsub(/ /,"-",base); \
line = curr base} \
{print substr(line,(lgth-$1)+1,lgth)}' file


Compare the solution above: this time sprintf and gsub build a string of 10 asterisks ("curr") and a string of 10 hyphens ("base").
The 2 strings are concatenated in "line" as the 20-character line "**********----------" (line = curr base).
"line" is then chopped with substr to give the correct number of asterisks and hyphens. The starting point is the (lgth-$1)+1-th character. For the first line in "file", this is character (10-6)+1, or character 5. The first 4 characters (asterisks) are ignored. The substring length is 10 (lgth) from that 5th character. Here's how that looks with pipes marking the substring boundaries:
**** | ******---- | ------
Finally, the substring of "line" is printed.

from me (user2138595):

awk -v OFS="*" '{NF=($1+1); $1=""; \
print gensub(/ /,"-","gorblimey",sprintf("%-10s",$0))}' file


-v OFS="*" sets the output field separator to an asterisk.
NF=($1+1) defines the number of fields in the line as 1 more than the value in the first input field. All fields except the first are empty, and if you printed the first line, it would now be "6******", i.e. 1 filled field, 6 empty fields and 6 field separators.
$1="" deletes the first field in the line, leaving only asterisks.
sprintf("%-10s",$0) formats the line as 10 characters, left-justified. The missing characters on the right are blank spaces.
gensub(/ /,"-","gorblimey",sprintf("%-10s",$0)) replaces all blank spaces in the sprintf-formatted line with hyphens. Why "gorblimey"? Well, the GNU AWK manual says if the third argument to gensub is a string beginning with 'g or 'G' (short for "global"), then all occurrences of the searched-for regex will be replaced, and "gorblimey" begins with "g".
print prints the line as modified by gensub.

Update. Reader HÃ¥kan Winbom offers another AWK solution:

awk '{printf "%.*s", $1,"************" ; \
printf "%.*s\n",10-$1,"----------"} ' file

This one takes advantage of the "precision" modifier for printf. The modifier is a full stop (.), and if it's preceded by the string formatter (%s) and followed by an asterisk (*), it means print the second argument as a string, but only to the number of characters shown in the first argument.

Last update: 2021-01-30
The blog posts on this website are licensed under a
Creative Commons Attribution-NonCommercial 4.0 International License