Recursive sum function in bash - bash

I have a problem with writing it in bash... I know how it works in C++, but I have trouble implementing it in bash. Here's what I got:
sum()
{
let minusOne=$1-1
let result=sum $minusOne +$1
}

You need an exit condition. In bash, $((...)) is arithmetic expansion, and $(...) is command substitution (see the man page).
sum() {
if (( $1 == 1 )); then
echo 1
return
fi
local minusOne=$(( $1 - 1 ))
echo $(( $1 + $(sum $minusOne) ))
}
A non-recursive way to write a sum function:
sum() {
set -- $(seq 1 $1)
local IFS=+
echo "$*" | bc
}

Here is a function that will give you a sum of numbers provided as arguments. The following prints "10":
#!/bin/bash
sum() {
local total=0
for number in "$#"; do
(( total += number ))
done
echo $total
}
sum 1 2 3 4

Related

Recursion in bash(fibonacci sequence)

The main problem my code doesn't do echo every time(fibonacci sequence)
#!/bin/bash
function fib(){
if [ $1 -le 0 ]; then
echo 0
elif [ $1 -eq 1 ]; then
echo 1
else
echo $[`fib $[$1 - 2]` + `fib $[$1 - 1]` ]
fi
}
fib $1
i was expecting it will do echo every time. It shows:
~/Bash$ ./fn.sh 12
144
but i need it to show like this:
~/Bash$ ./fn.sh 12
0
1
1
2
3
5
8
13
21
34
55
89
144
Your function is consuming the output of its invocation via backticks (command substitution). Only the last output is sent to the terminal. Your function will only return the n-th number of the Fibonacci sequence.
If you want to return all the numbers of the sequence up to a certain point, you can use a loop:
for i in $(seq "$i"); do
fib "$i"
done
Another method might be:
#!/bin/bash
fibseq () {
echo "$1"
if (($3 > 0)); then fibseq "$2" $(($1 + $2)) $(($3 - 1)); fi
}
if (($1 > 0)); then fibseq 0 1 "$1"; fi
Note that this is just a loop disguised as recursion. This method is much more efficient than the naive recursive version to compute Fibonacci sequences. Arguments to fibseq function: $3 serves as a counter, $1 and $2 are the last two Fibonacci numbers.
As an aside note, you can replace the $(($1 + $2)) with $(bc <<< "$1 + $2") if you want arbitrary-precision arithmetic.
Finally, don't use the $[…] form for arithmetic expansion. Use $((…)) instead.

How is the correct way to exapand braces in a "for loop" BASH to positional parameters?

When I try write this function in BASH
function Odds () for i in {1..${##}..2} ; do echo $i; done
I expected for an output like
1 3 5...
depending on number of arguments passed to the function.
But the efective output is the string with ${##} expanded.
Ex
{1..5..2}
I have some conjectures but... any away... what is happing and how to avoid this and get the desirable output?
The problem is that brace expansion is done before parameter substitution. Consequently, brace expansion can't be used with variables.
Option 1: seq
If you have seq installed (this is common on Linux), try:
function Odds () { seq 1 2 "${##}"; }
seq 1 2 "${##}" returns numbers starting with 1 and incremented by 2 each time until ${##} is reached.
For example:
$ Odds a b c d e
1
3
5
Note: The function notation isn't necessary and limits compatibility (it's not POSIX). Odds can be defined without it:
Odds () { seq 1 2 "${##}"; }
Option 2: bash
Alternatively, just using bash, define:
function Odds () { for ((i=1; i<=${##}; i=i+2)); do echo "$i"; done; }
This produces the same output:
$ Odds a b c d e
1
3
5
Option 3: POSIX
For widest compatibility, use a function that meets the POSIX standard:
Odds() { i=1; while [ "$i" -le "$#" ]; do echo "$i"; i=$((i+2)); done; }
Try this:
function Odds () { for i in {1..${#}..2} ; do echo $i; done }
or
function Odds () { for i in {1..${1}..2} ; do echo $i; done }

bash : If branch for two different loop

I am writing a for loop. But the loop is dependent on the content of positional argument.
If the positional arguments are seq 2 1 10, the loop is for i in $(seq 2 1 10)
If the positional arguments are purely numbers such as 1 2 5 7 10, then the loop is for i in 1 2 5 7 10.
I tried this, but it didn't work:
test () {
if [[ $1 == seq ]]
then
for i in $(seq $2 $3 $4)
else
for i in $#
fi
do
echo $i
done
}
I also tried this:
test2 () {
if [[ $1 == seq ]]
then
sss="for i in $(seq $2 $3 $4)"
else
sss="for i in $#"
fi
$sss
do
echo $i
done
}
also doesn't work.
So my questions are:
I know I could write explicit two loop inside if. But if loop content is large, this is a waste of code space. Is there any better way?
In my second attempt, why doesn't $sss expand to a for sentence and get parsed properly by bash?
Save the list of numbers in an array.
test () {
if [[ $1 == seq ]]
then
numbers=($(seq "$2" "$3" "$4"))
else
numbers=("$#")
fi
for i in "${numbers[#]}"
do
echo $i
done
}
In my second attempt, why doesn't $sss expand to a for sentence and get parsed properly by bash?
A variable can be expanded into a command to run, but not into a flow control construct like a for loop or if statement. Those need to be written out directly, they can't be stored in variables. If you try, bash will attempt to run a command named for--that is, it will look in /bin, /usr/bin, etc., for a binary named for.
An alternative to using arrays as in John Kugelman's answer is to use set -- to change the positional parameters:
test ()
{
if [[ $1 == seq ]]; then
set -- $(seq $2 $3 $4)
fi
for i; do
echo $i
done
}
Note that for i is equivalent to for i in "$#".
John already mentioned why it didn't work - variable interpolation and splitting happens after control flow is parsed.

Recursive Fibonacci in Bash script

This is my attempt:
#!/bin/bash
function fibonacci(){
first=$1
second=$2
if (( first <= second ))
then
return 1
else
return $(fibonacci $((first-1)) ) + $(fibonacci $((second-2)) )
fi
}
echo $(fibonacci 2 0)
I think i'm having trouble with the else statement. I get the error return: +: numeric argument required
on line 14.
The other problem that i'm having is that the script doesn't display any numbers even if i do echo $(fibonacci 0 2). I thought it would display a 1 since i'm returning a 1 in that case. Can someone give me a couple of tips on how to accomplish this?
After checking out some of your answers this is my second attempt.. It works correctly except that it displays the nth fibonacci number in the form 1+1+1+1 etc. Any ideas why?
#!/bin/bash
function fibonacci(){
first=$1
second=$2
if (( first <= second ))
then
echo 1
else
echo $(fibonacci $((first-1)) ) + $(fibonacci $((first-2)) )
fi
}
val=$(fibonacci 3 0)
echo $val
My final attempt:
#!/bin/bash
function fibonacci(){
first=$1
if (( first <= 0 ))
then
echo 1
else
echo $(( $(fibonacci $((first-1)) ) + $(fibonacci $((first-2)) ) ))
fi
}
val=$(fibonacci 5)
echo $val
thanks dudes.
#!/bin/bash
function fib(){
if [ $1 -le 0 ]; then
echo 0
elif [ $1 -eq 1 ]; then
echo 1
else
echo $[`fib $[$1-2]` + `fib $[$1 - 1]` ]
fi
}
fib $1
The $(...) substitution operator is replaced with the output of the command. Your function doesn't produce any output, so $(...) is the empty string.
Return values of a function go into $? just like exit codes of an external command.
So you need to either produce some output (make the function echo its result instead of returning it) or use $? after each call to get the value. I'd pick the echo.
As Wumpus said you need to produce output using for example echo.
However you also need to fix the recursive invocation.
The outermost operation would be an addition, that is you want:
echo $(( a + b ))
Both a and b are substitutions of fibonacci, so
echo $(( $(fibonacci x) + $(fibonacci y) ))
x and y are in turn arithmetic expressions, so each needs its own $(( )), giving:
echo $(( $(fibonacci $((first-1)) ) + $(fibonacci $((second-2)) ) ))
If you are confused by this, you should put the components into temporary variables and break down the expression into parts.
As to the actual fibonacci, it's not clear why you are passing 2 arguments.
Short version, recursive
fib(){(($1<2))&&echo $1||echo $(($(fib $(($1-1)))+$(fib $(($1-2)))));}
While calculating fibonacci numbers with recursion is certainly possible, it has a terrible performance. A real bad example of the use of recursion: For every (sub) fibonacci number, two more fibonacci numbers must be calculated.
A much faster, and simple approach uses iteration, as can be found here:
https://stackoverflow.com/a/56136909/1516636

Parameter input works, but pipe doesn't

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. :-)

Resources