I'm trying to write a script that takes integers as command line arguments, squares them and then shows the squares and the sum of the squares. Here's what I have:
#!/bin/bash
if [ $# = 0 ]
then
echo "Enter some numbers"
exit 1
fi
sumsq=0 #sum of squares
int=0 #Running sum initialized to 0
count=0 #Running count of numbers passed as arguments
while (( $# != 0 )); do
numbers[$int]=$1 #Assigns arguments to integers array
square=$(($1*$1)) #Operation to square argument first arg by itself
squares[$int]=$square #Square of each argument
sumsq=$((sumsq + square)) #Add square to total
count=$((count+1)) #Increment count
shift #Remove the used argument
done
echo "The squares are ${squares[#]}"
echo "The sum of the squares is $sumsq"
exit 0
I have it all working as it should except for the end where I need to display the squares. It is only printing the last square for some reason. I've tried changing e${squares[#]}" to ${squares[*]}", as well as double-quoting it, but it still only prints the last square. At some point, it printed the first square (which is expected) but I must have made a change somewhere and now it only seems to print the last one.
Any advice would be appreciated. Thanks!
The easy thing to do is simply delete the int variable altogether and replace it with count which you already increment within your loop, e.g.
sumsq=0 #sum of squares
count=0 #Running count of numbers passed as arguments
while (( $# != 0 )); do
numbers[$count]=$1 #Assigns arguments to integers array
square=$(($1*$1)) #Operation to square argument first arg by itself
squares[$count]=$square #Square of each argument
sumsq=$((sumsq + square)) #Add square to total
count=$((count+1)) #Increment count
shift #Remove the used argument
done
Example Use/Output
Making the change above:
$ bash squares.sh 1 2 3 4
The squares are 1 4 9 16
The sum of the squares is 30
(no, you don't such at programming, that's one issue new script writers could stare at for hours and not see -- and why running with bash -x scriptname to debug is helpful)
Update Following Comment Re: Difficulty
Above I only posted the changes needed. If it is not working for you, then I suspect a typo somewhere. Here is the complete script you can simply save and run:
#!/bin/bash
if [ $# = 0 ]
then
echo "Enter some numbers"
exit 1
fi
sumsq=0 #sum of squares
count=0 #Running count of numbers passed as arguments
while (( $# != 0 )); do
numbers[$count]=$1 #Assigns arguments to integers array
square=$(($1*$1)) #Operation to square argument first arg by itself
squares[$count]=$square #Square of each argument
sumsq=$((sumsq + square)) #Add square to total
count=$((count+1)) #Increment count
shift #Remove the used argument
done
echo "The squares are ${squares[#]}"
echo "The sum of the squares is $sumsq"
exit 0
As per the example above, run the script as:
bash squares.sh 1 2 3 4
(replace squares.sh with whatever name you gave to the file) If you have any problems, let me know because this is working quite well here, e.g.
bash squares.sh 1 2 3 4 5 6 7 8 9
The squares are 1 4 9 16 25 36 49 64 81
The sum of the squares is 285
Try this.
#!/usr/bin/env bash
case $# in
0) printf 'give me something!' >&2; exit 1;; ##: If no argument given exit!
esac
sumsq=0 #sum of squares
int=0 #Running sum initialized to 0
count=0 #Running count of numbers passed as arguments
while (( $# )); do
case $1 in
*[!0-9]*) printf '%s is not an integer!' >&2 "$1" ##: If argument is not an int. exit!
exit 1;;
esac
numbers[$((int++))]=$1 #Assigns arguments to integers array increment int by one
square=$(($1*$1)) #Operation to square argument first arg by itself
squares[$((int++))]=$square #Square of each argument increment int by one
sumsq=$((sumsq + square)) #Add square to total
count=$((count++)) #Increment count
shift #Remove the used argument
done
echo "The squares are ${squares[#]}"
echo "The sum of the squares is $sumsq"
Just iterate over given arguments
#!/bin/bash
for N in "$#"; { # iterate over args
squares+=( $((N*N)) ) # will create and then apend array squares
}
sqstr="${squares[#]}" # create string from array
echo "The squares are $sqstr" # string of squares will be substituted
echo "The sum of the squares is $((${sqstr// /+}))" # string will be substituted,
# spaces will be changed to +
# and sum will be calculated
Usage
$ ./test 1 2 3 4 5
The squares are 1 4 9 16 25
The sum of the squares is 55
Updated version
#!/bin/bash
for N do squares+=($((N*N))); done # iterate over args, create and then apend array squares
sqstr="${squares[#]}" # create string from array
echo "The squares are $sqstr" # string of squares will be substituted
echo "The sum of the squares is $((${sqstr// /+}))" # string will be substituted,
# spaces will be changed to +
# and sum will be calculated
Related
I've been killing myself over this trying to figure it out, and I know it's probably super simple, so hoping a new pair of eyes can help. I have a Bourne shell (sh) script that I'm writing and it takes a list of integers as the input (and amount from 1 integer up, ideally I'd like to take both positive and negative ints). I'm trying to do an error check for the case if someone inputs something other than an integer. They could input "1 2 3 4 5 a" and it'd give an error, because the a is not an int.
I have an error check for no inputs that works, and I have code that does stuff to the list of integers themselves, but even when strings are given it still gets to my final code block.
I currently have a for loop to iterate through each item in the list of integers, then an if loop to give the error message if the argument in question isn't an int. I've tried a few different versions of this, but this is the most recent one so I've put it below.
for i in $#; do
if [ $i -ge 0 ] 2>/dev/null; then
echo "Invalid input: integers only."
exit 1
fi
done
#!/bin/sh
#
for i in "$#"
do
case "${i#[-+]}" in
0)
echo cannot be 0?
exit 1
;;
*[!0-9]* | '')
echo not int
exit 2
;;
esac
done
echo i\'m ok
This should work, for both positive and negative ints. And if you admit that 0 is an integer, just delete the first case.
Almost duplicate: BASH: Test whether string is valid as an integer?
And here is a good answer for posix. https://stackoverflow.com/a/18620446/7714132
You could use a regex:
my_script.sh
for i in $# ; do
if ! [[ "$i" =~ ^-?[0-9]+$ ]] ; then
echo "Invalid input: integers only."
exit 1
fi
done
Example:
$ sh my_script.sh 1 2 3 4
$ sh my_script.sh 1 2 -12
$ sh my_script.sh 1 2 -12-2
Invalid input: integers only.
$ sh my_script.sh 1 2 a b
Invalid input: integers only.
Explanation of the regex:
^: beginning of the string
-?: 0 or 1 times the character -
[0-9]+: 1 or more digit
$: end of the string
In POSIX sh, you can match your string against a glob with case:
#!/bin/sh
for i
do
case "$i" in
*[!0-9]*)
echo "Integers only" >&2
exit 1
esac
done
echo "Ok"
Here's how it runs:
$ ./myscript 1 2 3 4 5
Ok
$ ./myscript 1 2 3 4 5 a
Integers only
The problem with your approach is primarily that you're checking for success rather than failure: [ will fail when the input is invalid.
Trying to write a script that accepts exactly one command line argument and must be a positive number. I have the one argument down, but can't figure out the positive number.
I have the following:
#!/bin/ksh
NUMBER=$1
# Accepts only one argument or does not execute.
if [ "$#" != 1 ]
then
echo "error: program must be executed with 1 argument."
exit
else
if [ "$#" < 1 ] # **NEED HELP HERE**. Check for negative number.
then
echo "error: argument must be a positive number."
fi
fi
do
echo -n $NUMBER
if [ $NUMBER -gt 1 ]
then
echo -n ", "
fi
NUMBER=$(($NUMBER - 1))
done
echo
...Need help with identifying if command line argument is a negative number.
Thanks
Verify it is a number using by checking the whether it is only digits:
#!/bin/ksh
NUMBER=$1
# Accepts only one argument or does not execute.
if [ "$#" != 1 ]
then
echo "error: program must be executed with 1 argument."
exit
else
case $NUMBER in
''|*[!0-9]*)
echo "error: argument must be a positive number."
exit
;;
esac
fi
while ! [ $NUMBER -lt 1 ]
do
echo -n $NUMBER
if [ $NUMBER -gt 1 ]
then
echo -n ", "
fi
NUMBER=$(($NUMBER - 1))
done
echo
Following will work:
test "$NUMBER" -ge 0 && printf "NOT NEGATIVE" || printf "NEGATIVE"
Assuming we're only talking about positive (base-10) integers ... and excluding commas, exponential notation and leading plus sign (+) ...
# test for ${NUMBER} containing anything other than digits 0-9
if [[ "${NUMBER}" = #(*[!0-9]*) ]]
then
echo "error: argument is not a positive number."
else
echo "success: argument is a positive number"
fi
Obviously it becomes a bit more interesting as you strip away the assumptions on what's defined as a *valid* positive number.
Expanding on the solution proposed by sqrt163 ... a simple function should allow us to handle exponential notation, float/real and numbers with a leading plus sign (+), while allowing the calling process to filter out messages about syntax errors ...
$cat test.sh
#!/bin/ksh
NUMBER=$1
isnumber()
{
# following should generate a non-zero for negative and non-numerics,
# while allowing the parent process to filter out syntax errors
[[ "${1}" -gt 0 ]]
}
# direct isnumber() output to /dev/null since we're only interested
# in the return code, and we want to mask any syntax issues if
# isnumber() is called with a non-numeric argument; unfortunately,
# for a valid numeric that contains a comma (eg, NUMBER = 1,000)
# our isnumber() call will fail
if isnumber ${NUMBER} >/dev/null 2>&1
then
echo "${NUMBER} is a positive number"
else
echo "${NUMBER} is not a positive number"
fi
And some tests ...
$ for s in 1 1.33 1e5 1e-4 +1 +1.65 +1e4 1,000 -1 -1.234 -1e5 -1,000 a b a1b ab1 1ab "a b c"
do
test.sh "$s"
done
1 is a positive number
1.33 is a positive number
1e5 is a positive number
1e-4 is a positive number
+1 is a positive number
+1.65 is a positive number
+1e4 is a positive number
1,000 is not a positive number
-1 is not a positive number
-1.234 is not a positive number
-1e5 is not a positive number
-1,000 is not a positive number
a is not a positive number
b is not a positive number
a1b is not a positive number
ab1 is not a positive number
1ab is not a positive number
a b c is not a positive number
This leaves us (I believe) with just the issue of an input containing 1 or more commas, which could be handled by stripping out the comma(s) using whatever method fits your fancy.
to loop through a continous list of numbers in bash I can do
for s in $(seq 1 5);do
echo ${s}
done
to loop through a continous list of numbers leaving a given number out in python I can do:
list = [s2 for s2 in range(6)[1:] if s2 != s1]
for s1 in list:
print s1
where list contains all numbers in range except s1
How do I do the same in bash?
Just use continue to skip this step:
for s in {1..5} # note there is no need to use $(seq...)
do
[ "$s" -eq 3 ] && continue # if var is for example 3, jump to next loop
echo "$s"
done
This returns:
1
2
4 # <--- 3 is skipped
5
From Bash Reference Manual → 4.1 Bourne Shell Builtins:
continue
continue [n]
Resume the next iteration of an enclosing for, while, until, or select
loop. If n is supplied, the execution of the nth enclosing loop is
resumed. n must be greater than or equal to 1. The return status is
zero unless n is not greater than or equal to 1.
Add a short circuit evaluation, || (logical OR) :
for s in $(seq 1 5); do
(( s == 3 )) || echo "$s"
done
(( s == 3 )) checks if $s is equal to 3, if not (||) echo the number.
With the reverse check ($s not equal to 3) and logical AND (&&):
for s in $(seq 1 5); do
(( s != 3 )) && echo "$s"
done
The classic way, if with test ([), non-equity test:
for s in $(seq 1 5); do
if [ "$s" -ne 3 ]; then
echo "$s"
fi
done
Reverse test, equity check:
for s in $(seq 1 5); do
if [ "$s" -eq 3 ]; then
continue
fi
echo "$s"
done
continue will make the loop control to go at the top rather than evaluating the following commands.
There is also a bash keyword [[ which behaves similarly in most cases but more robust.
You can use BASH arithmetic construct ((...)) like this:
s1=3 # skip this
s2=6 # upper count
for ((i=1; i<s2; i+=(i==s1-1?2:1) )); do echo $i; done
1
2
4
5
About: i+=(i==s1-1?2:1)
In the for loop instead of always incrementing i by 1 here we are incrementing i by 2 when i is 1 less then the number to be skipped.
Alternatively solution using BASH array:
arr=({1..5}) # populate 1 to 5 in an array
unset arr[s1-1] # delete element s1-1
# print the array
printf "%s\n" "${arr[#]}"
1
2
4
5
I just started learning bash. I know there are advanced ways of solving this problem but I can't use any advanced methods. I recently finished a lecture on loops and I'm expected to know how to solve this. But after 3+ hours of reading I can't find a solution.
Here's the prompt:
Create a script that will take in x amount of numbers from a user (minimum 5 numbers) via the use of positional variables. Then do the following:
Count how many numbers were inputted
Add up all the numbers
Multiply all the numbers
Find the average of all the numbers
read -p "Enter at least 5 integers"
#Check to make sure integers entered are indeed integers (how?)
#Check to make sure at least 5 integers were entered (how?)
#for total number of integers entered, add into a sum
#for total number of integers entered, find total product
#find average of all integers entered
My main issues are with the checks. Also, how do I assign an indefinite number of values to variables? Thanks for any help. I've looked around but have found no beginner solution to this.
#!/bin/bash
declare -i sum=0
declare -i counter=0
declare -i product=1
if [[ $# -lt 5 ]]
then
echo "Please enter atleast 5 positional parameters"
exit 1
fi
for num in "$#" #$# means all positional parameters, $1, $2, $3 and so on
do
#checking if the entered positional parameters are digits using regular expression, [0-9]+ means match any digit 1 or more times
if [[ "$num" =~ ^[0-9]+$ ]]
then
sum=$(($sum+$num))
product=$(($product*$num))
((counter++))
else
echo "$num is not a number"
fi
done
echo
echo "Result: "
echo "count of numbers: $counter"
echo "sum of numbers: $sum"
echo "average of numbers: "$(echo "scale=4;$sum/$counter" | bc)
echo "product of numbers: $product"
Output:
$ ./script.bash 1 2 3 4 5 abcd
abcd is not a number
Result:
count of numbers: 5
sum of numbers: 15
average of numbers: 3.0000
product of numbers: 120
$ ./script.bash 1 2 3
Please enter atleast 5 positional parameters
$ ./script.bash 56 78 235 67 466
Result:
count of numbers: 5
sum of numbers: 902
average of numbers: 180.4000
product of numbers: 32048758560
I tried to create a shell script, which sum the given numbers. If there is no given parameter, then it tries to read the pipe output, but I get an error.
#!/bin/sh
sum=0
if [ $# -eq 0 ]
then
while read data
do
sum=`expr $sum + $data`
done
else
for (( i = 1 ; i <= $#; i++ ))
do
sum=`expr $sum + ${!i}`
done
fi
echo $sum
This works: sum 10 12 13
But this one doesn't: echo 10 12 13| sum
Thanks in advance,
Here you go (assuming bash, not sh):
#!/bin/bash
sum=0
if (( $# == 0 )); then
# Read line by line
# But each line might consist of separate numbers to be added
# So read each line as an array!
while read -a data; do
# Now data is an array... but if empty, continue
(( ${#data[#]} )) || continue
# Convert this array into a string s, with elements separated by a +
printf -v s "%s+" ${data[#]}
# Append 0 to s (observe that s ended with a +)
s="${s}0"
# Add these numbers to sum
(( sum += s ))
done
else
# If elements come from argument line, do the same!
printf -v s "%s+" $#
# Append 0 to s (observe that s ended with a +)
s="${s}0"
# Add these numbers to obtain sum
(( sum = s ))
fi
echo $sum
You can invoke it thus:
$ echo 10 12 13 | ./sum
35
$ ./sum 10 12 13
35
$ # With several lines and possibly empty lines:
$ { echo 10 12 13; echo; echo 42 22; } | ./sum
99
Hope this helps!
Edit. You might also be interested in learning cool stuff about IFS. I've noticed that people tend to confuse # and * in bash. If you don't know what I'm talking about, then you should use # instead of *, also for array subscripts! In the bash manual, you'll find that when double quoted, $* (or ${array[*]}) expands to all the elements of the array separated by the value of the IFS. This can be useful in our case:
#!/bin/bash
sum=0
if (( $# == 0 )); then
# Read line by line
# But each line might consist of separate numbers to be added
# So read each line as an array!
while read -a data; do
# Now data is an array... but if empty, continue
(( ${#data[#]} )) || continue
# Setting IFS=+ (just for the sum) will yield exactly what I want!
IFS=+ sum=$(( sum + ${data[*]} ))
done
else
# If elements come from argument line, do the same!
# Setting IFS=+ (just for the sum) will yield exactly what I want!
IFS=+ sum=$(( $* ))
fi
echo $sum
Gniourf now exits from teacher mode. :-)