5

I am trying to create a script that will count down to 0 from what ever number I give it. Below is my script and basically nothing happens, no error message, I merely get the standard command line prompt back.

#!/bin/bash
#countdown
#counts down to 0 from whatever number you give it
#displaying a number each second

NUM=${1:-0}
if [ $NUM -gt 0 ]
then
   while [ $NUM -gt 0 ]
   do
      if [ -f /usr/bin/banner ]
      then
         /usr/bin/banner "$NUM"
      else
         echo $NUM
      fi
         NUM=$(($NUM-1))
         sleep 2
   done
fi
7
  • What is NUM=${1:-0}? – Ken Sharp Apr 13 '15 at 15:28
  • That is a part of the original script my teacher gave me. Is it unnecessary? – HankG Apr 13 '15 at 15:29
  • So I made a remark of it: #NUM=${1:-0} – HankG Apr 13 '15 at 15:33
  • 2
    I tested it. Your script works. – A.B. Apr 13 '15 at 15:40
  • 5
    I suppose you forgot to give it a number as parameter, e.g. ./countdown 30. – Jos Apr 13 '15 at 15:52
3
#!/bin/bash

printf "Type an integer number: " && read NUM
if [ $NUM -gt 0 ]
then
   while [ $NUM -ge 0 ]
   do
      if [ -f /usr/bin/banner ]
      then
         /usr/bin/banner "$NUM"
      else
         echo $NUM
      fi
         NUM=$(($NUM-1))
         sleep 2
   done
fi

output:

:~$ ./countdown.sh 
Type an integer number: 10
10
9
8
7
6
5
4
3
2
1
0

explanation:
⠀1. line 3 prompts the user to input an integer number and reads it into the variable NUM.
⠀2. Changed the -gt in line 6 to -ge so that it counts down to zero.
⠀3. The output is displayed in banner format if sysvbanner is installed or else as text if it isn't.

6
  • Sure, that works too. But his original works as well. – A.B. Apr 13 '15 at 15:44
  • 2
    You might want to add some explanation, and also why OP's script didn't work. – Jacob Vlijm Apr 13 '15 at 15:49
  • Thanks Karel this did the trick! And thanks A.B. but I don't know why but it would not work for me without the printf line Karel suggested. Thanks again everyone! :) – HankG Apr 13 '15 at 15:49
  • "... why OP's script didn't work ..." :) – A.B. Apr 13 '15 at 15:57
  • 1
    @JacobVlijm The reason why the OP's script didn't work is because of line 6 that says: NUM=${1:-0}. This line has a pattern of the form ${var:-value}in it which means 'if $var is set to any value except the empty string, use it; otherwise, use value instead'. When the command ./countdown.sh is executed without an integer number after it $var is an empty string, so it uses value instead which is 0. That means that NUM gets an input of zero, so there is no countdown to output. In order to give NUM some integer input > 0, I replaced that line with code asking to input an integer. – karel Apr 14 '15 at 11:07
3

Improved and commented code:

#!/bin/bash

num=${1:-undefined}                                   # If $1 (the first argument passed to the script) is set, then num=$1, else num=undefined.
cmd=$(which {banner,echo} | head -1 | xargs basename) # If banner is installed, then cmd=baner, else cmd=echo.

until [[ "$num" =~ ^[0-9]+$ ]]; do                    # Until $num become a valid number (loop will not be executed if $1 is set):
    read -p "Type a number: " num                         # Ask the user for a valid number.
done                                                  # End of the until loop.

for ((num;num>=0;num--)); do                          # Loop using $num as variable; while $num is greater or equal than zero; num=$num-1.
   $cmd $num                                              # Runs $cmd (banner or echo) passing $num as argument.
   sleep 1                                                # Stop the program execution for one second.
done                                                  # End of the for loop.

The above code will include zero in the countdown, if do you want to stop when the countdown reaches 1, then you only need to make a few changes:

  1. In the 6th line, change ^[0-9]+$ by ^[1-9]+[0-9]*$ so it looks like this:

    until [[ "$num" =~ ^[1-9]+[0-9]*$ ]]; do              # Until $num become a valid number (loop will not be executed if $1 is set):
    
  2. In the 10th line, remove the = sign so it looks like this (I've updated also the comment):

    for ((num;num>0;num--)); do                           # Loop using $num as variable; while $num is strictly greater than zero; num=$num-1.
    

Your original program doesn't work because:

  • You not passed a number as argument to the program.
  • A example invocation of this command is ./countdown 5 where 5 is the number.
  • If do you want to handle this, you can add a else to your code (look a the five last lines):

    #!/bin/bash
    #countdown
    #counts down to 0 from whatever number you give it
    #displaying a number each second
    
    NUM=${1:-0}
    if [ $NUM -gt 0 ]
    then
       while [ $NUM -gt 0 ]
       do
          if [ -f /usr/bin/banner ]
          then
             /usr/bin/banner "$NUM"
          else
             echo $NUM
          fi
             NUM=$(($NUM-1))
             sleep 2
       done
    else
        echo "Error: number not specified."
        echo "Usage: $0 <number>"
        exit 1
    fi
    

NUM=${1:-0} means:

${PARAMETER:-WORD}

If the parameter PARAMETER is unset (never was defined) or null (empty), this one expands to WORD, otherwise it expands to the value of PARAMETER, as if it just was ${PARAMETER}.

echo "Your home directory is: ${HOME:-/home/$USER}."
echo "${HOME:-/home/$USER} will be used to store your personal data."

If HOME is unset or empty, everytime you want to print something useful, you need to put that parameter syntax in.

Source: http://wiki.bash-hackers.org/syntax/pe#use_a_default_value

In your case, it means that, if you passed a argument to the script, NUM will be equal to that argument, else, NUM will be equal to 0

5
  • Why does the number regex allow negative numbers? I really like the rest of your improved code though. – Oli Apr 14 '15 at 11:19
  • @Oli: Sorry, was a mistake, fixed! – 0x2b3bfa0 Apr 14 '15 at 11:30
  • @Oli: Can you fix my answer? English isn't my mother tongue. – 0x2b3bfa0 Apr 14 '15 at 12:33
  • 1
    COMMAND=$(which banner || which echo) falls back to the echo executable rather than the shell builtin. In bash (this script already uses bash-specific features) you have the echo builtin and may as well use it. Also, if command substitution is used here then in the odd case of whitespace in the full path of banner ($PATH entries may contain whitespace, though it's best avoided) it should probably used as "$COMMAND" $NUM later in the script. I suggest using which banner && COMMAND=banner || COMMAND=echo or similar, instead. – Eliah Kagan Apr 15 '15 at 12:07
  • @EliahKagan: Ok, I corrected it, however, what about this code COMMAND=$(eval 'which '{banner,echo}'||' :) or this one COMMAND=$(which {banner,echo} | head -n1) ? ;-) – 0x2b3bfa0 Apr 16 '15 at 8:51
2

The NUM=${1:-0} line means that the variable NUM is set to $1 if a parameter is passed to the script, and to 0 if no parameter is passed at all. That explains why you have no output at all; the threshold is always set to 0 if the script is executed without passing a parameter to it, e.g.:

bash <script_name>

*<script_name> = name of your bash script;

or:

./<script_name>

*<script_name> = name of your bash script;

So you what you really need to do is to just pass the threshold number to your script upon execution, i.e.:

bash <script_name> <threshold_number>

*<script_name> = name of your bash script; <number> = thresold number

or:

./<script_name> <threshold_number>

*<script_name> = name of your bash script; <number> = thresold number

1
  • 6
    Whoever downvoted this post should be aware that he/she would be very welcome to explain what is wrong with it – kos Apr 13 '15 at 16:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.