As I said in a comment, your test is wrong: you need
for (( j=1; j<=i; j++ ))
instead of
for (( j=1; j>=i; j++ ))
Otherwise, this loop is only executed when i=1
, and it becomes an infinite loop.
Now if you want another way to solve that, in a much better way:
#!/bin/bash
[[ $1 = +([[:digit:]]) ]] || { printf >&2 'Argument must be a number\n'; exit 1; }
number=$((10#$1))
for ((;number>=1;--number)); do
printf -v spn '%*s' "$number"
printf '%s\n' "${spn// /*}"
done
Why is it better? first off, we check that the argument is really a number. Without this, your code is subject to arbitrary code injection. Also, we make sure that the number is understood in radix 10 with 10#$1
. Otherwise, an argument like 09
would raise an error.
We don't really need an extra variable for the loop, the provided argument is good enough. Now the trick: to print n times a pattern, a cool method is to store n spaces in a variable with printf
: %*s
will expand to n spaces, where n is the corresponding argument found by printf
.
For example:
printf '%s%*s%s\n' hello 42 world
would print:
hello world
(with 42 spaces).
Editor's note: %*s
will NOT generally expand to n spaces, as evidenced by above output, which contains 37 spaces.
Instead, the argument that *
is mapped to,42
, is the field width for the s
field, which maps to the following argument,world
, causing string world
to be left-space-padded to a length of 42; since world
has a character count of 5, 37 spaces are used for padding.
To make the example work as intended, use printf '%s%*s%s\n' hello 42 '' world
- note the empty string argument following 42
, which ensures that the entire field is made up of padding, i.e., spaces (you'd get the same effect if no arguments followed 42
).
With printf
's -v
option, we can store any string formatted by printf
into a variable; here we're storing $number
spaces in spn
. Finally, we replace all spaces by the character *
, using the expansion ${spn// /*}
.
Yet another possibility:
#!/bin/bash
[[ $1 = +([[:digit:]]) ]] || { printf >&2 'Argument must be a number\n'; exit 1; }
printf -v s '%*s' $((10#1))
s=${s// /*}
while [[ $s ]]; do
printf '%s\n' "$s"
s=${s%?}
done
This time we construct the variable s
that contains a bunch of *
(number given by user), using the previous technique. Then we have a while
loop that loops while s
is non empty. At each iteration we print the content of s
and we remove a character with the expansion ${s%?}
that removes the last character of s
.