How do I print a horizontal line of characters like ----?
There are many different ways, depending on how fancy you'd like to be. This page is a restoration of content from the now-defunct bash-hackers wiki, with some additional text.
The simplest way: Just print it
printf '%s\n' --------------------
This does exactly what it looks like, no more, and no less. If you want a variable number of hyphens, continue reading.
The iterative way
This one simply loops 20 times, always draws a dash, finally a newline. The number of loop iterations may be changed by passing a value in a variable.
for ((i = 0; i < 20; i++)); do printf %s - done echo
The printf command uses unbuffered I/O, so it actually writes a hyphen once per loop iteration. This may be unappealing in some applications. Also, some people may prefer more compact code for personal reasons.
Implicit printf looping
This one uses the printf command to print an empty field with a minimum field width of 20 characters. The text is padded with spaces, but since there is no text, you get 20 spaces. The spaces are then converted to - by the tr command.
printf '%20s\n' | tr ' ' -
Without an external command, using the (non-POSIX) substitution expansion and -v option:
printf -v res %20s printf '%s\n' "${res// /-}"
Sizing it to the terminal
This is a variant of the above that uses the $COLUMNS variable if it's set, or tput cols otherwise, to find the width of the terminal and set that number as the minimum field witdh.
printf '%*s\n' "${COLUMNS:-$(tput cols)}" '' | tr ' ' -
Fancy printf tricks
This one is a bit tricky. The format for the printf command is %.0s, which specifies a field with the maximum length of zero. After this field, printf is told to print a dash. You might remember that it's the nature of printf to repeat, if the number of conversion specifications is less than the number of given arguments. With brace expansion {1..20}, 20 arguments are given (you could easily write 1 2 3 4 … 20, of course!). Following happens: The zero-length field plus the dash is repeated 20 times. A zero length field is, naturally, invisible. What you see is the dash, repeated 20 times.
# Note: you might see that as ''%.s'', which is a (less documented) shorthand for ''%.0s'' printf '%.0s-' {1..20}; echo
If the 20 is variable, you can use eval to insert the expansion (take care that using eval is potentially dangerous if you evaluate external data):
# Note: COLUMNS should be sanity-checked first, and a safe PATH should be ensured before calling tput. eval printf %.0s- '{1..'"${COLUMNS:-$(tput cols)}"\}; echo
Or restrict the length to 1 and prefix the arguments with the desired character.
eval printf %.1s '-{1..'"${COLUMNS:-$(tput cols)}"\}; echo
You can also do it the crazy ormaaj way™. It completely depends on Bash due to its brace expansion evaluation order and array parameter parsing details. As above, the eval only inserts the COLUMNS expansion into the expression and isn't involved in the rest, other than to put the _ value into the environment of the _[0] expansion. This works well since we're not creating one set of arguments and then editing or deleting them to create another as in the previous examples.
_=- command eval printf %s '"${_[0]"{0..'"${COLUMNS:-$(tput cols)}"'}"}"'; echo
Note: This example no longer works in bash 5.0 or higher.
Using parameter expansions
Preparing enough dashes in advance, we can then use a non-POSIX subscript expansion:
hr=---------------------------------------------------------------\ ---------------------------------------------------------------- printf '%s\n' "${hr:0:${COLUMNS:-$(tput cols)}}"
A more flexible approach, and also using modal terminal line-drawing characters instead of hyphens:
This one builds up the line variable until it's at least large enough for the terminal, then prints a substring that's exactly the width of the terminal. The modal line-drawing characters may not work in all terminals.