Bash Script - return actually returns something else - linux

My Original Code:
# Factorial Using Recursion
res=1
fact()
{
x=$1
if [ $x -le 1 ]
then
echo "Actual - $res"
return `expr $res`
else
#echo $x
res=$(($res * $x))
echo "($res)"
fact $[$x-1]
fi
}
fact $1
echo "Factorial of $1 = $?"
The result (stored in $res) is certainly what I want and is also correct. But somehow when it is being returned and caught by $? it becomes erroneous.

The maximum value of a return code is 255 so there's no way that you can return the factorial of most integers.
Instead, your function should output the value to standard output, e.g. using echo. Capture the output of the function like result=$(fact "$x").
If you want to output other messages while the function is executing then you can print to standard error using echo 'whatever' >&2.
res=1
fact()
{
x=$1
if [ "$x" -le 1 ]
then
echo "$res"
else
res=$((res * x))
fact "$((x - 1))"
fi
}
I've also fixed up your syntax a little bit.

Related

Linux Shell Scripting recursive exponentation

I am writing a script that takes 2 numbers as an input and uses recursion to power on number to the power of the other, simple exponentiation. However I am new to scripting and cannot figure out where my syntax is errored here.
Here is the script
#!/bin/bash
echo "Enter number: "
read number
echo "Enter power: "
read power
echo "Powering $number to power of $power!"
exp () {
if [ $2 = 1 ]
then
return $1
fi
return $1 * $(exp $1 $2-1 )
}
result=$(exp $number, $power)
echo "Result: $result"
Currently, it kind of freezes, im not sure if I use the parameters correctly (in terms of syntax).
You need $(( )) to force arithmetic evaluation. Then you can do it with return values:
number=2 power=7
exp () {
if [ $2 -eq 1 ]; then return $1; fi
exp $1 $(($2-1))
return $(($1 * $?))
}
exp $number $power; result=$?
echo "Result: $result"
but it's a bad idea, because shells kind of reserve nonzero return values to communicate failure (e.g. the above solution
will "break" set -e).
More idiomatically, you can use stdout:
set -e
number=2 power=7
exp () {
if [ $2 -eq 1 ]; then echo $1; return; fi
echo $(($1 * $(exp $1 $(($2-1)) ) ))
}
result=$(exp $number $power)
echo "Result: $result"
but that's kind of inefficient with all the subshells.
Best to avoid the recursion and simply loop:
number=2 power=7
exp () {
local res=1 i=0;
while [ $i -lt $2 ]; do res=$((res*$1)); i=$((i+1)); done
echo $res
}
exp $number $power

Read string and convert to INT (BASH)

I have a simple script in Bash to read a number in a file and then compare it with a different threshold. The output is this:
: integer expression expected
: integer expression expected
OK: 3
My code is this:
#!/bin/bash
wget=$(wget http://10.228.28.8/ -O /tmp/wget.txt 2>/dev/null)
output=$(cat /tmp/wget.txt | awk 'NR==6')
#output=7
echo $output
if [ $output -ge 11 ];then
echo "CRITICAL: $output"
exit 2
elif [ $output -ge 6 ] && [ $output -lt 11 ];then
echo "WARNING: $output"
exit 1
else
echo "OK: $output"
exit 0
fi
rm /tmp/wget.txt
I know what is the problem, I know that I'm reading a string and I try to compare a int. But I don't know how can I do to read this file and convert the number to read in a int var..
Any ideas?
The problem occurs when $output is the empty string; whether or not you quote the expansion (and you should), you'll get the integer expression required error. You need to handle the empty string explictly, with a default value of zero (or whatever default makes sense).
wget=$(wget http://10.228.28.8/ -O /tmp/wget.txt 2>/dev/null)
output=$(awk 'NR==6' < /tmp/get.txt)
output=${output:-0}
if [ "$output" -ge 11 ];then
echo "CRITICAL: $output"
exit 2
elif [ "$output" -ge 6 ];then
echo "WARNING: $output"
exit 1
else
echo "OK: $output"
exit 0
fi
(If you reach the elif, you already know the value of $output is less than 11; there's no need to check again.)
The problem also occurs, and is consistent with the error message, if output ends with a carriage return. You can remove that with
output=${output%$'\r'}
There are a couple of suggestions from my side regarding your code.
You could explicitly tell bash the output is an integer
declare -i output # See [1]
Change
output=$(cat /tmp/wget.txt | awk 'NR==6') # See [2]
may be better written as
output=$(awk 'NR==6' /tmp/wget.txt )
Change
if [ $output -ge 11 ]
to
if [ "0$output" -ge 11 ] # See [4]
or
if (( output >= 11 )) # Better See [3]
References
Check bash [ declare ].
Useless use of cat. Check [ this ]
Quoting [ this ] answer :
((...)) enable you to omit the dollar signs on integer and array variables and include spaces around operators for readability. Also empty variable automatically defaults to 0 in such a statement.
The zero in the beginning of "0$output" help you deal with empty $output
Interesting
Useless use of cat is a phrase that has been resounding in SO for long. Check [ this ]
[ #chepner ] has dealt with the empty output fiasco using [ bash parameter expansion ] in his [ answer ], worth having a look at.
A simplified script:
#!/bin/bash
wget=$(wget http://10.228.28.8/ -O /tmp/wget.txt 2>/dev/null)
output=$(awk 'NR==6' </tmp/wget.txt )
output="$(( 10#${output//[^0-9]} + 0 ))"
(( output >= 11 )) && { echo "CRITICAL: $output"; exit 2; }
(( output >= 6 )) && { echo "WARNING: $output"; exit 1; }
echo "OK: $output"
The key line to cleanup any input is:
output="$(( 10#${output//[^0-9]} + 0 ))"
${output//[^0-9]} Will leave only digits from 0 to 9 (will remove all non-numeric chars).
10#${output//[^0-9]} Will convert output to a base 10 number.
That will correctly convert numbers like 0019
"$(( 10#${output//[^0-9]} + 0 ))" Will produce a zero for a missing value.
Then the resulting number stored in output will be compared to limits and the corresponding output will be printed.
In BASH, It is a good idea to use double brackets for strings:
if [[ testing strings ]]; then
<whatever>
else
<whatever>
fi
Or double parenthesis for integers:
if (( testing ints )); then
<whatever>
else
<whatever>
fi
For example try this:
var1="foo bar"
if [ $var1 == 'foo bar' ]; then
echo "ok"
fi
Result:
$ bash: [: too many arguments
Now, this:
var2="foo bar"
if [[ $a == "foo bar" ]]; then
echo "ok"
fi
Result:
ok
For that, your code in BASH:
if [[ $output -ge 11 ]]; then
echo "CRITICAL: $output"
exit 2
elif [[ $output -ge 6 ]]; then
echo "WARNING: $output"
exit 1
else
echo "OK: $output"
exit 0
fi

Script with parameter

I supossed to make a script that given an number it count to 0, I managed to do this and it's working:
#!/bin/bash
echo -n "type a number: "
read number; echo
while [ $number -ge 0 ]; do
echo -n "$number"
number=$((number-1))
done
echo
Well, I changed it because I need to pass the number by an parameter ex: "./script 5" and it must show the countdown till 0, but its getting in looping. I kind new on all it script/stack what Im doing wrong?
#!/bin/bash
if [ "$*" = "" ]; then
echo
echo "not correct"
echo "must be a int number"
echo
exit
fi
while [ "$1" -ge 0 ]; do
echo "$1"
cont='expr $1-1'
done
echo
You're always using [ "$1" -ge 0 ] as your condition, but the value you actually modify/update is cont, not $1. (Moreover, you modify it based on the value of $1, which isn't changing, so you only ever set $cont to one less than the original value of $1).
Consider:
#!/bin/bash
[[ $1 ]] || { printf '%s\n' "First argument must be an integer" >&2; exit 1; }
for ((i=$1; i>=0; i--)); do
echo "$i"
done
...and note, among the various changes:
We're consistently referring to the first argument passed as $1, rather than also sometimes referring to it as $*
When we select a variable to modify ($i, here, rather than $cont), we use that same variable in our tests, and also as the source for modification in the loop.
Using expr for math is antiquated; POSIX sh allows $(( )) to create a math context, and bash extends this to also allow C-style for loops in a math context.

How to compare a variable with a variable minus a constant in a linux shell script?

I want to compare a variable with another variable minus a constant in a linux shell script.
In cpp this would look like this:
int index = x;
int max_num = y;
if (index < max_num - 1) {
// do whatever
} else {
// do something else
}
In the shell i tried the following:
index=0
max_num=2
if [ $index -lt ($max_num - 1) ]; then
sleep 20
else
echo "NO SLEEP REQUIRED"
fi
I also tried:
if [ $index -lt ($max_num-1) ]; then
...
if [ $index -lt $max_num - 1 ]; then
...
if [ $index -lt $max_num-1 ]; then
...
but non of these versions works.
How do you write such a condition correctly?
Regards
The various examples that you tried do not work because no arithmetic operation actually happens in any of the variants that you tried.
You could say:
if [[ $index -lt $((max_num-1)) ]]; then
echo yes
fi
$(( expression )) denotes Arithmetic Expression.
[[ expression ]] is a Conditional Construct.
Portably (plain sh), you could say
if [ "$index" -lt "$((max_num-1))" ]; then
echo yes
fi
Short version
[ "$index" -lt "$((max_num-1))" ] && echo yes;
[ is the test program, but requires the closing ] when called as [. Note the required quoting around variables. The quoting is not needed when using the redundant and inconsistent bash extensions cruft ([[ ... ]]).
In bash, a more readable arithmetic command is available:
index=0
max_num=2
if (( index < max_num - 1 )); then
sleep 20
else
echo "NO SLEEP REQUIRED"
fi
The strictly POSIX-compliant equivalent is
index=0
max_num=2
if [ "$index" -lt $((max_num - 1)) ]; then
sleep 20
else
echo "NO SLEEP REQUIRED"
fi

Bash string comparison not working

I have the following Bash function:
checkForUpdates() {
checkLatest
ret=$?
if [ $ret != 0 ]; then
return $ret
fi
count=0
for i in $(ssh $__updateuser#$__updatehost "ls $__updatepath/*${latest}*"); do
file="${i##$__updatepath}"
echo "$file" >> $__debuglog
if [ -f $__pkgpath/$file ]; then
remoteHash=$(ssh $__updateuser#$__updatehost "md5sum -b < $__updatepath/${file}")
localHash=$(md5sum -b < $__pkgpath/$file)
echo "${remoteHash:0:32} = ${localHash:0:32}" >> $__debuglog
if [ "${remoteHash:0:32}" != "${localHash:0:32}" ]; then
files[$count]=$file
count=$(($count + 1))
echo "Hashes not matched, adding $i" >> $__debuglog
fi
else
files[$count]=$file
count=$(($count + 1))
echo "$file missing" >> $__debuglog
fi
done
# Verify that the files array isn't empty.
if [ $count != 0 ]; then
return 0
else
return 33
fi
}
For some reason, the remoteHash/localHash comparison always returns true. I added the echo so that I could see the values of the hashes and they are definitely different and I can't figure out where I'm going wrong. I have tried different operators with no success and it is driving me crazy!
this isn't related to your question but more of general advice, first and most important you shouldn't parse the output of ls instead use find -print0 here's an example: http://mywiki.wooledge.org/BashFAQ/001
also consider using [[ instead of [ see: http://mywiki.wooledge.org/BashFAQ/031
now regarding your code, this part:
checkLatest
ret=$?
if [ $ret != 0 ]; then
return $ret
fi
could be written simply as:
checkLatest || return
and you don't need to keep a counter on the index of the array, if you initialize the var as an empty array like files=() you can then append elements to it with files+=("$file") you can get the count with "${#files[#]}"

Resources