Formatting the output: Underlining
Solution 1
The core of your question is building a string consisting entirely of underscores that is of the same length as an existing string. In recent enough versions of bash, ksh or zsh you can build this string with the ${VARIABLE//PATTERN/REPLACEMENT}
construct: underlines=${word//?/_}
. But this construct doesn't exist in ksh88.
In any shell, you can use tr
instead. POSIX-compliant implementations of tr
let you write this:
underlines=$(printf %s "$word" | tr -c '_' '[_*]')
I think Solaris 10 has a POSIX-compliant tr
by default, but there might be a historical implementation (compatible with earlier Solaris releases).
Historical implementations of tr
might not understand the [x*]
syntax, but they tend to accept the following syntax instead (which isn't guaranteed by POSIX), to mean “replace everything that isn't a newline by a _
”:
underlines=$(echo "$word" | tr -c '\010' '_')
underlines=${underlines%_}
And here's a slightly crazy method that doesn't use any loop or external program and should work in any Bourne shell (at least since set -f
was introduced — though running in an empty directory would mitigate the lack of set -f
). Unfortunately, it only works if the string doesn't contain any whitespace.
set -f # turn off globbing
IFS=$word # split at any character in $word
set a $word # split $word into one word between each character, i.e. empty words
shift # remove the leading a (needed in case $word starts with -)
IFS=_
underlines=$* # join the empty words, separated with the new value of $IFS
A more complex variant deals with whitespace, but only if there isn't any consecutive whitespace sequence. I don't think you can go any further with this trick, since sequences of whitespace characters in IFS
are always collapsed.
set -f
unset IFS; set a $0 # split at whitespace
IFS=$*; set $* # split into empty words
IFS=_; underlines=$* # collect the empty
Solution 2
In newer shells you can do printf %s\\n "${word//?/-}"
. I don't think ksh88 has that particular expansion.
If you don't mind an extra process, you could do printf %s\\n "${word}" | sed -e 's/./-/g'
.
Your approach is fine as well, although I would make the following tiny change:
print_underlined () {
word=$1
printf %s\\n "$word"
i=${#word}
while (( i )); do
printf -
(( i = i - 1 ))
done
echo
}
And for a completely different approach, use the terminal's ability to display true underlines, if available:
tput smul; printf %s\\n "$word"; tput rmul
Of course, that approach only works if you know the terminal the script runs on supports it.
Solution 3
If the output is to a terminal that supports underlined graphic rendition, as is the case with most terminal emulators nowadays (including dtterm but not wscons) and quite a few real terminals historically, then true underline qualifies as "more elegant":
print_underlined () {
word=$1
tput smul # set mode underline
print -r -- "$word"
tput rmul # reset mode underline
}
On a system with termcap and not terminfo, which is luckily not the case for Solaris 10, the capability names are different:
print_underlined () {
word=$1
tput us
print -r -- "$word"
tput ue
}
On an old Teletype Model 37, one would have done underline by backspacing and overstriking with the underscore character, _
.
print_underlined () {
word=$1
print -r -- "$word" | sed -e 's/./&'$'\b''_/g'
}
This is better done the other way around if the output is not actually to a TTY-37. On an actual TTY-37 it does not matter which order things are overstruck, obviously. On "glass TTYs" overstriking does not exist, so it is better to "overstrike" the underscore with the character to be underlined, rather than vice versa.
print_underlined () {
word=$1
print -r -- "$word" | sed -e 's/./_'$'\b''&/g'
}
Strictly speaking, termcap/terminfo parameterize what the characters to emit are for underline current position and move on one and for backspace to previous position. In practice, the termcap/terminfo databases only define the uc
capability for a handful of very rare terminals, and do not define it for the terminals and terminal emulators that you will encounter in the 21st century (or even the late 20th), making this parameterization largely pointless.
Ironically, the TTY-37 way is still the way to underline in some parts of Unix, all these decades later. Feed such input to more
or less
, or even ul
, and they will render it in their outputs as true underlining, using the terminal's underlining control sequences.
print_underlined wibble | more
You can even feed it to the colcrt
tool, which will turn the (sadly still widely used) TTY-37 underlining into underlining with minus signs.
$ print_underlined wibble | colcrt
wibble
------
$
Solution 4
I found this simply by googling it:
underline() { echo $1; echo "${1//?/${2:--}}";}
Basically the same thing but much more compact. If you find it confusing, more info on the curly bracket substitution can be found here. Very similar to the sed syntax.
Solution 5
This is a POSIX compliant way that will work on Solaris 10 & ksh88:
print_underlined () {
printf "%s\n%s\n" "$1" $(printf "%s\n" "$1" | sed "s/./-/g")
}
$ print_underlined "hello world"
hello world
-----------
Related videos on Youtube
rahmu
Updated on September 18, 2022Comments
-
rahmu almost 2 years
I wrote the following function in
ksh
that prints its first argument to the screen and underlines it with the appropriate ammount of-
character:print_underlined () { word=$1 echo $word i=${#word} while [[ i -gt 0 ]]; do printf "-" (( i = $i - 1 )) done printf "\n" }
example:
$ print_underlined foobar foobar ------ $
I wonder if there is a simpler and more elegant way to display an underlined word to the screen.
For the record I am using:
- Solaris 10
- ksh88
-
rahmu about 12 yearsUnfortunately, except for the
sed
process, none of the solutions suggested seem to work on such an old shell (although I tested them inbash
and they seem to work). The decrement operator in particular is annoyingly missing from the shell, hence my original clumsy syntax. Oh and thetput
solution didn't work either... it was too good to be true :( -
jw013 about 12 years@rahmu Sorry, I got carried away and didn't realize ksh88 lacked the decrement operator :). A pure POSIX-
sh
approach would not be much different from what you wrote, just with different test and arithmetic operators. Thetput
approach is entirely dependent on the terminal. If you want your script to be portable, it's probably not a good idea to assume things about the user's terminal. -
nikitautiu about 12 yearsAlso a second optional argument can be provided for the underline character.
-
rahmu about 12 yearsNope won't work on my old shell. The answer is similar to the first one suggested by jw013 and is analogous to what I am looking for.
-
Stéphane Chazelas over 4 years
${var//pattern/replacement}
was introduced by ksh93. It wasn't there in ksh88 -
Marius over 4 yearsThe comment and example about Solaris 10 is incorrect. It uses the terminfo names. Actually, about half the answer needs a rewrite...