1

OK so Ive been at this for a couple days,im new to this whole bash UNIX system thing i just got into it but I am trying to write a script where the user inputs an integer and the script will take that integer and print out a triangle using the integer that was inputted as a base and decreasing until it reaches zero. An example would be:

reverse_triangle.bash 4
****
***
** 
*

so this is what I have so far but when I run it nothing happens I have no idea what is wrong

#!/bin/bash
input=$1
count=1
for (( i=$input; i>=$count;i-- ))
       do 
           for (( j=1; j>=i; j++ ))
              do 
                   echo -n "*"
            done
       echo 
       done
exit 0

when I try to run it nothing happens it just goes to the next line. help would be greatly appreciated :)

user1118321
  • 25,567
  • 4
  • 55
  • 86
vickes
  • 65
  • 3
  • 14

2 Answers2

2

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 sfield, 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.

mklement0
  • 382,024
  • 64
  • 607
  • 775
gniourf_gniourf
  • 44,650
  • 9
  • 93
  • 104
  • Some great techniques in there. Two observations: (a) by using `[[ $1 = +([[:digit:]]) ]]`, i.e., an extended glob, you're relying on bash 4.1+; to also make it work on 3.2+, you could use regex matching: `[[ $1 =~ ^[[:digit:]]+$ ]]`. (cont'd below) – mklement0 Feb 20 '15 at 05:09
0

Building on gniourf_gniourf's helpful answer:

The following is simpler and performs significantly better:

#!/bin/bash

count=$1  # (... number-validation code omitted for brevity)

# Create the 1st line, composed of $count '*' chars, and store in var. $line.
printf -v line '%.s*' $(seq $count)

# Count from $count down to 1.
while (( count-- )); do
  # Print a *substring* of the 1st line based on the current value of $count.
  printf "%.${count}s\n" "$line"
done
  • printf -v line '*%.s' $(seq $count) is a trick that prints * $count times, thanks to %.s* resulting in * for each argument supplied, irrespective of the arguments' values (thanks to %.s, which effectively ignores its argument). $(seq $count) expands to $count arguments, resulting in a string composed of $count * chars. overall, which - thanks to -v line, is stored in variable $line.
  • printf "%.${count}s\n" "$line" prints a substring from the beginning of $line that is $count chars. long.
Community
  • 1
  • 1
mklement0
  • 382,024
  • 64
  • 607
  • 775