Counting down in a loop to zero by the number being given - bash

I am trying to write a while loop to determine the number is being given to count down to 0. Also, if there's no argument given, must display "no parameters given.
Now I have it counting down but the last number is not being 0 and as it is counting down it starts with the number 1. I mush use a while loop.
My NEW SCRIPT.
if [ $# -eq "0" ] ;then
echo "No paramters given"
else
echo $#
fi
COUNT=$1
while [ $COUNT -gt 0 ] ;do
echo $COUNT
let COUNT=COUNT-1
done
echo Finished!
This is what outputs for me.
sh countdown.sh 5
1
5
4
3
2
1
Finished!
I need it to reach to 0

#Slizzered has already spotted your problem in a comment:
You need operator -ge (greater than or equal) rather than -gt (greater than) in order to count down to 0.
As for why 1 is printed first: that's simply due to the echo $# statement before the while loop.
If you're using bash, you could also consider simplifying your code with this idiomatic reformulation:
#!/usr/bin/env bash
# Count is passed as the 1st argument.
# Abort with error message, if not given.
count=${1?No parameters given}
# Count down to 0 using a C-style arithmetic expression inside `((...))`.
# Note: Increment the count first so as to simplify the `while` loop.
(( ++count ))
while (( --count >= 0 )); do
echo $count
done
echo 'Finished!'
${1?No parameters given} is an instance of shell parameter expansion
bash shell arithmetic is documented here.

You should also validate the variable before using it in an arithmetic context. Otherwise, a user can construct an argument that will cause the script to run in an infinite loop or hit the recursion limit and segfault.
Also, don't use uppercase variable names since you risk overriding special shell variables and environment variables. And don't use [ in bash; prefer the superior [[ and (( constructs.
#!/usr/bin/env bash
shopt -s extglob # enables extended globs
if (( $# != 1 )); then
printf >&2 'Missing argument\n'
exit 1
elif [[ $1 != +([0-9]) ]]; then
printf >&2 'Not an acceptable number\n'
exit 2
fi
for (( i = $1; i >= 0; i-- )); do
printf '%d\n' "$i"
done
# or if you insist on using while
#i=$1
#while (( i >= 0 )); do
# printf '%d\n' "$((i--))"
#done

Your code is far from being able to run. So, I don't know where to start to explain. Let's take this small script:
#!/bin/sh
die() {
echo $1 >&2
exit 1;
}
test -z "$1" && die "no parameters given"
for i in $(seq $1 -1 0); do
echo "$i"
done
The main part is the routine seq which does what you need: counting from start value to end value (with increment in between). The start value is $1, the parameter to our script, the increment is -1.
The test line tests whether there is a parameter on the command line - if not, the script ends via the subroutine die.
Hth.

There are a number of ways to do this, but the general approach is to loop from the number given to an ending number decrementing the loop count with each iteration. A C-style for loop works as well as anything. You will adjust the sleep value to get the timing you like. You should also validate the required number and type of input your script takes. One such approach would be:
#!/bin/bash
[ -n "$1" ] || {
printf " error: insufficient input. usage: %s number (for countdown)\n" "${0//*\//}"
exit 1
}
[ "$1" -eq "$1" >/dev/null 2>&1 ] || {
printf " error: invalid input. number '%s' is not an integer\n" "$1"
exit 1
}
declare -i cnt=$(($1))
printf "\nLaunch will occur in:\n\n"
for ((i = cnt; i > 0; i--)); do
printf " %2s\n" "$i"
sleep .5
done
printf "\nFinished -- blastoff!\n\n"
exit 0
Output
$ bash ./scr/tmp/stack/countdown.sh 10
Launch will occur in:
10
9
8
7
6
5
4
3
2
1
Finished -- blastoff!
Your Approach
Your approach is fine, but you need to use the value of COUNT $COUNT in your expression. You also should declare -i COUNT=$1 to tell the shell to treat it as an integer:
#!/bin/bash
if [ $# -eq "0" ] ;then
echo "No paramters given"
else
echo -e "\nNumber of arguments: $#\n\n"
fi
declare -i COUNT=$1
while [ $COUNT -gt 0 ] ;do
echo $COUNT
let COUNT=$COUNT-1
done
echo -e "\nFinished!\n"

Related

How to fix count that doesn't work in while loop

I have been trying to resolve an issue where my loop's count should decrease, however nothing is working. I need to create a while loop that will read over a given amount of times. For instance, if I enter in "files.txt -a 3" in the terminal, I need my loop to repeat "Enter in a string: " 3 times. With my code below, I am only able to get it to loop once. I am not to sure where to put the counter and I can say that I have put it everywhere. Inside the if statement, in inside of the for loop, and inside the while loop but none seem to work. The number that the user will put is held in the $count variable.
#!/bin/bash
if ["$1" = "-a" ]
then
read in user String and save into file
fi
while [ "$count" > 0 ]
do
for i in $count
do
if [ "-a" ]
then
read -p "Enter in a string: " userSTR
echo userSTR >> files.txt
count=$(($count - 1))
fi
done
done
For conditional expression you need to use [[ expression ]], e.g. this will loop four times:
count=4
while [[ $count > 0 ]] ; do
echo "$count"
count=$(( $count - 1 ))
done
To fetch the count from the command-line argument, you could replace the assignment count=4 above with the following, parsing the command-line arguments:
if [ $# -lt 2 ] ; then
echo "Usage: $0 -a [count]"
exit 1
fi
if [ "$1" = "-a" ] ; then
shift
count=$1
fi

Shell script to output the largest

How to write a script which will receive a list of parameters and output the number that is the largest. If no parameters are supplied, output an error message.
I wrote the following code to check if no parameters are supplied, output an error message.
#!/bin/bash
if [ "$#" -eq "0" ]
then
echo "No arugments supplied"
else
echo "$# Parameter"
But I dont know how to continue...
Keep a current max. Loop over the input, updating max if necessary. At the end you'll have the global maximum.
Untested:
#!/usr/bin/bash
if [ $# -eq 0 ]; then
echo "Usage: $0 NUMBERS" >&2
exit 1;
fi
max="$1"
shift
while [ $# -gt 0 ]; do
if [ "$1" -gt "$max" ]; then
max="$1"
fi
shift
done
echo "$max"
Use sort -n (numeric) and -r (reverse) and then just pick the first line of the output -- like
#!/bin/bash
if [ "$#" -eq "0" ]
then
echo "No arugments supplied"
else
echo "$# Parameter"
for i in $*; do echo ${i}; done | sort -nr | head -1
fi
Now the only problem you are facing is when the the input (the arguments) are not numbers -- but you didn't say anything about what should happen then.
Here's a pseudocode you could implement:
save the first param in a variable called max
loop over the params
if the param is greater than max, update max
print max
Here's an example loop that prints all parameters:
for num; do
echo $num
done
And here's an example of comparing values:
if (( num > max )); then
echo $num is greater than $max
fi
This should be more than enough help to complete your homework.
Well since others have already gave you the actual solution, here's mine too:
#!/bin/bash
if (( $# == 0 )); then
echo "No arugments supplied"
exit 1
fi
max=$1
for num; do
if (( num > max )); then
max=$num
fi
done
echo $max

Bash script if statement not working

I'm trying to write a script that adds three specified arguments together, and if there are no arguments outputs: "No arguments".
The trouble is that "No arguments" is always output even when there are three arguments.
I am very new to shell script.
Here is my script:
#!/bin/sh
if [[("$#"==0)]]; then
echo "No arguments specified"
exit 1
fi
sum=0
sum=$(expr $1 + $2 + $3)
echo "$sum"
exit 0
Either change your shebang to #!/bin/bash and use
if (( $# == 0 )); then
or use the POSIX-compatible [:
if [ $# -eq 0 ]; then
Don't forget that [ and [[ are both commands, not syntax, so as with any other command, you need to separate the arguments you pass to the command with spaces.
If you are using bash features, such as [[, you should always use the #!/bin/bash shebang, as otherwise you will run into problems.
As pointed out in the comments below the other answer, it is possibly a better idea to check that you have been passed three arguments:
#!/bin/bash
if (( $# < 3 )); then
echo "Insufficient number of arguments specified"
exit 1
fi
sum=$(( $1 + $2 + $3 ))
echo "$sum"
I have made a couple of other changes to your script, such as not initialising sum to 0 and using the more modern $(( )) to evaluate the sum of the variables.
I guess what you ask for is:
if [ $# -eq 0 ];

Evaluating return value of bash function as integer

I've been trying to write a bash function to check if a variable is an integer. The is_number function in the script below does this check.
When I run it though (with a test value of 1234) I get the following error message:
./test_n2.sh: line 13: [: Checking 1234
1234: integer expression expected
n is not an integrer
I'm confused: the function returns $?, which in my one liner tests with grep is correctly set to 0 for integral values. The integer comparison in the main block is done as [ "$retval" -eq 0 ], which AFAIK is the correct form for testing integers.
Can someone please help?
TIA.
#!/usr/bin/env bash
function is_number {
number=$1
echo "Checking $number"
echo $number | /bin/egrep '^[0-9]+$'
return $?
}
n=1234
retval=`is_number $n`
if [ "$retval" -eq 0 ]; then
echo n is an integer
exit 0
fi
echo n is not an integrer
exit 1
When you assign a variable using backticks or $(...), it gets the standard output of the command. The return value of a command is put in the variable $?. So you should do:
is_number $n
retval=$?
Or you can simply write:
if is_number $n; then
echo n is an integer
exit 0
fi
You don't need grep for this at all, bash on its own will happily do the job, and more efficiently:
is_number() {
# Check if $1 only consists of digits
[[ $1 = +([[:digit:]]) ]]
}
Then you can use it as:
if is_number 123; then
echo "it's a number!"
else
echo "it's not a number!"
fi
Use this with the following caveat: if you need to perform arithmetic, be aware that leading zero's will force bash to interpret the string as a number in radix 8. Hence the following is an error:
a=09
if is_number "$a"; then
echo $((a+1));
else
echo "Not a number"
fi
Your snippet is hence better written as:
#!/usr/bin/env bash
shopt -s extglob
is_number() {
# Check if $1 only consists of digits
[[ $1 = +([[:digit:]]) ]]
}
n=1234
if is_number "$n"; then
echo "n is an integer"
exit 0
else
echo "n is not an integer"
exit 1
fi
or if you need to save the return value of the function is_number:
#!/usr/bin/env bash
shopt -s extglob
is_number() {
# Check if $1 only consists of digits
[[ $1 = +([[:digit:]]) ]]
}
n=1234
is_number "$n"
ret=$?
if ((ret==0)); then
echo "n is an integer"
else
echo "n is not an integer"
fi
exit "$ret"

Shell script read user input

I got stuck on this shell script code where it requires user to enter the group number and find the largest and the average out of that group number.
My code ATM only works with passing the group number as a command-line argument. How to prompt for the group number as user input?
read n
if [ $n -ge 1 ]; then
sum=0
count=$n
max=-1000
if [ $max -lt $1 ]; then
max=$1
fi
while [ $n -ge 1 ]; do
case $1 in
[0-9] | [1-9][0-9])
sum=`expr $sum +$1`;;
-[1-9] | -[1-9][0-9])
sum=`expr $sum + $1`;;
done
if [ $count -gt 0 ]; then
avg=`expr $sum / $count`
echo The largest number is $max
echo The average number is $avg
From your comments it seems you would like to read values from stdin rather than from the command line. To do that in Bash you use the read builtin:
read -ep "Enter group number: " group
printf "Entered %d\n" $group
For interactive prompting it is usually put in the test part of a while loop where you can break if the input is invalid:
shopt -s extglob
while read -ep "Enter group number: " group; do
case $group in
?(-)+([0-9])) # valid input
# compute average here
*) break ;; # not valid input
esac
done
See help read for more information.
You're working way too hard, and your script is way too verbose. Try:
#!/bin/sh
test $# -gt 0 || { echo Please enter at least one argument >&2; exit 1; }
max=0
for x; do
test "$max" -lt $x && max=$x
test $? -gt 1 && exit 1
: $(( count += 1 ))
: $(( sum += x ))
done
echo max = $max
printf "avg = "
expr $sum / $count
Since you used expr in your script, I'm doing the same, but be aware that all of the arithmetic will be done in the integers (so the reported average will be the greatest integer less than the actual average, and non-integer input will be considered an error). This solution relies on test returning a value greater than 1 when it encounters an error (eg, non integer input), which is the behavior specified by the open group.
Also note that this puts the error message on stderr (where error messages belong) and returns a non-zero value to indicate that the script failed.

Resources