I've written the following minimal example to demonstrate
The following does NOT work:
#! /bin/bash
n=1 m=2
while (( n < m ))
echo "$n $m"
(( n=n+1 ))
do
continue
done
The following DOES work:
#! /bin/bash
n=1 m=2
while true
echo "$n $m"
(( n=n+1 ))
do
if (( n < m ))
then
continue
else
break
fi
done
I understand why the second form works. I do not understand why the first form doesn't work.
I wrote these scripts thinking they would be equivalent. (Produce the same output.) However the first loops infinity! Why does this happen?
First, a quick look at the documentation:
$ help while
while: while COMMANDS; do COMMANDS; done
Expand and execute COMMANDS as long as the final command in the
`while' COMMANDS has an exit status of zero.
Note the exact text: the final command in the while COMMANDS is the one whose exit status counts. We'll return to that later.
In your original code:
while (( n < m ))
echo "$n $m"
(( n=n+1 ))
do
continue
done
...you aren't checking whether (( n < m )) is true as your condition; instead, you're checking whether (( n = n + 1 )) is true.
This is true because everything before the do is composed to form the condition that determines whether to continue running the loop, and the exit status of a series of commands separated by newlines or ;s is the exit status of the last command in that series. The only case where (( n = n + 1 )) would not be true (assuming no contents that can't be coerced to an integer or otherwise cause an error) is if the initial value were below 0, and thus the result were 0 or less.
If you really want all those commands to be inside your condition, you could instead write:
while (( n < m )) && echo "$n $m" && (( n = n + 1 )); do :; done
...though of course that has somewhat different behavior (not 'echo'ing if the comparison has failed).
Consider instead:
while (( n < m )); do
echo "$n $m"
(( n++ ))
done
Related
I use the following and it loops continually. Why doesn't it stop when $i is below 10?
Thanks.
#! /bin/bash
#
n=10
nn=20
#
for (( i=$(($n+$nn)) ; ! i<=10 ; i -=2 ))
do
echo $i
sleep 1
if [ $i -le 0 ]
then
exit 99
fi
done
#
echo "Done with the value of i which started at $(($n+$nn)) is exited with i=$i"
You should simply use >:
for (( i=$(($n+$nn)) ; i > 10 ; i -=2 ))
Or try with parenthesis:
for (( i=$(($n+$nn)) ; !( i <= 10 ) ; i -=2 ))
Edited: I misread the doc (https://www.gnu.org/software/bash/manual/html_node/Shell-Arithmetic.html): logical and bitwise negation should work, but I suppose the <= has less priority than !.
The reason is because ! i is always less than ten. ! does take precedence over comparison operators. The order of precedence for operators is listed in man bash > ARITHMETIC EVALUATION.
! i <= 10 tests if the logical negation of i is less than or equal to ten. For all values of i except zero, where it's one, the logical negation of i is zero. So it will always be true.
Im trying to create a program that lists all catalan-numbers below or equal to an argument in a bash-script. This is what I currently have but its giving me a stackoverflow error (I believe the error must be in the for-loop, but I can't figure out why). I have made this program in java and it works so I think it must be some syntax error?
#!/usr/bin/env bash
pcat=0
Cat() {
res=0
if [ $1 -le 1 ]
then
echo 1
return 1
fi
for ((i=0; i<$1; i++))
do
var1=$(($1-($i+1)))
call1=$(Cat $i)
call2=$(Cat $var1)
res=$(( res+call1+call2 ))
done
echo ${res}
return res
}
while [ $pcat -lt $1 ]
do
Cat $pcat
pcat=$((pcat+1))
done
The line where you do return res is incorrect, return could deal only with numbers and number less than 128 in general.
Assuming what you meant was return $res, the script will run.
I managed to get the program working with a similar code to yours:
#!/bin/bash
catalan() {
local n=$1
#echo "called with $n" >&2
if (( n <= 1 )); then
res=1
else
res=0
for ((i=0; i<n; i++))
do
var1=$(( n-i-1 ))
call1=$(catalan $i)
call2=$(catalan $var1)
res=$(( res+call1*call2 ));
#echo ":$i:$var1: result Call1:$call1: and Call2:$call2: $res" >&2
done
fi
#echo "result is ${res}" >&2
echo "$res"
}
n=$1
until (( pcat > n ))
do catalan "$((pcat++))"
done
echo "all was done"
There was a second problem in that the values of Call1 and Call2 need to be multiplied, not added. Changed res+call1+call2 to:
res=$(( res+call1*call2 ))
But the resultant code was very slow. Just to calculate the tenth (10) catalan number the code took 16 seconds.
An entirely new program that keeps the values inside a single array: catarray.
As this:
#!/bin/bash
# some initial values to jump start the code:
catarray=( 1 1 2 5 )
#############################################################################
catalan(){
#echo "making call for $1" >&2
local n=$1
# ${#catarray[#]} is the count of values in catarray (last index + 1).
# if the number n to be found is not yet in the array of values of
# catarray then we need to calculate it. Else, we just print the value.
if (( n >= ${#catarray[#]} )); then
#echo "$n is bigger than ${#catarray[#]}" >&2
# this is a new number, lets loop up till we
# fill the array just up to this value
for (( i=${#catarray[#]};i<=n;i++)); do
#echo "fill index $i in array" >&2
# calculate the sum of all terms for catalan of $n.
for(( j=0;j<i;j++ )); do
(( catarray[i] += catarray[j] * catarray[i-j-1] ))
#echo "done math in $i for $j with ${catarray[j]} *
#echo "* ${catarray[i-j-1]} = ${catarray[i]}"
done
done
fi
# After making the math or else we just print the known value.
#printf 'result of catalan number is %s\n' "${catarray[n]}"
}
#############################################################################
catalan "$1"
printf '%s, ' "${catarray[#]}"; echo
Wich will execute the tenth (10) catalan number in just 4 milliseconds.
A lot of echos were included to "see" how the program works. You may unquote them.
There is a limit though, numbers in bash should fit in 64 bits (for 64 bit computers) or be less than (2^63-1). That makes the biggest catalan number possible the 35th.
$ catalan 35
1, 1, 2, 5, 14, 42, 132, 429, 1430, 4862, 16796, 58786, 208012, 742900,
2674440, 9694845, 35357670, 129644790, 477638700, 1767263190,
6564120420, 24466267020, 91482563640, 343059613650, 1289904147324,
4861946401452, 18367353072152, 69533550916004, 263747951750360,
1002242216651368, 3814986502092304, 14544636039226909,
55534064877048198, 212336130412243110, 812944042149730764,
3116285494907301262
But will only take ~20 miliseconds to do that.
I have a zsh script that should generate a bunch of Z-matrix files, but it exits the while loops after one iteration. N and M never increase
#!/bin/zsh
n=0.5
m=0.5
a=60.0
i=1
while [[ $n -le 1.5 ]];do
while [[ $m -le 1.5 ]];do
while [[ $a -le 160 ]];do
echo $i
echo "o" > H2O.$i.Z
eval echo "h 1" $n >> H2O.$i.Z
eval echo "h 1" $m "2" $a >> H2O.$i.Z
let i=$i+1
let a=$a+5.0
done
let m=$m+0.05
done
let n=$n+0.05
done
I'm new to zsh; is my syntax wrong?
The -le operator operates on integers. For floating point, use the built-in arithmetic.
I also believe you might actually want to re-initialize the values for m and a in the inner loops. Then the bug is that you only initialized them once at the beginning and the inner loop conditions on the second iteration were false.
You might use a for((;;)) loop to write this more compact:
#!/usr/local/bin/zsh
i=1
for ((n=0.5; n <= 1.5; n+=0.05)); do
for ((m=0.5; m <= 1.5; m+=0.05)); do
for ((a=60.0; a <= 160; a+=5.0)); do
echo $i $a $m $n
let i=$i+1
done
done
done
Note also that it is unwise to use floating point numbers as loop counts. Did you know that ten times 0.1 is hardly ever one? This might explain why you lose or gain an extra iteration.
Instead, you should iterate using an integer number N and compute the required floats as 0.5 + N * 0.05, for example.
The -le operator only supports integer comparisons. For floating-point, you need to use the ((...)) command. Also, you don't need eval, and you can combine the three echos into one command so you only have to redirect once.
while (( n < 1.5 )); do
while (( m < 1.5 )); do
while (( a <= 160 )); do
echo $i
{ echo "o"; echo "h 1" $n; echo "h 1" $m "2" $a; } > H20.$i.Z
((i+=1))
((a+=5.0))
done
((m+=0.05))
done
((n+=0.5))
done
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
I've written a script to calculate the bandwidth usage of an OpenVZ container over time and suspend it if it uses too much too quickly. Here is the script so far:
#!/bin/bash
# Thresholds are in bytes per second
LOGDIR="/var/log/outbound_ddos"
THRESHOLD1=65536
THRESHOLD2=117964
while [ 1 ]
do
for veid in $(/usr/sbin/vzlist -o veid -H)
do
# Create the log file if it doesn't already exist
if ! test -e $LOGDIR/$veid.log; then
touch $LOGDIR/$veid.log
fi
# Parse out the inbound/outbound traffic and assign them to the corresponding variables
eval $(/usr/sbin/vzctl exec $veid "grep venet0 /proc/net/dev" | \
awk -F: '{print $2}' | awk '{printf"CTOUT=%s\n", $9}')
# Print the output and a timestamp to a log file
echo $(date +%s) $CTOUT >> $LOGDIR/$veid.log
# Read last 10 entries into arrays
i=0
tail $LOGDIR/$veid.log | while read time byte
do
times[i]=$time
bytes[i]=$byte
let ++i
done
# Time checks & calculations for higher threshold
counter=0
for (( i=0; i<9; i++ ))
do
# If we have roughly the right timestamp
if (( times[9-i] < times[8-i] + 20 ))
then
# If the user has gone over the threshold
if (( bytes[9-i] > bytes[8-i] + THRESHOLD2 * 10 ))
then let ++counter
fi
fi
done
# Now check counter
if (( counter == 9 ))
then vzctl stop $veid
fi
# Same for lower threshold
counter=0
for (( i=0; i<3; i++ ))
do
# If we have roughly the right timestamp
if (( times[3-i] < times[2-i] + 20 ))
then
# If the user has gone over the threshold
if (( bytes[3-i] > bytes[2-i] + THRESHOLD1 * 10 ))
then let ++counter
fi
fi
done
# Now check counter
if (( counter == 2 ))
then vzctl stop $veid
fi
done
sleep 10
done
I've checked the numbers in /var/log/outbound_ddos/vm101.log and they're increasing by more than the threshold, but nothing is happening.
I added some echo statements to try and figure out where the problem is and it seems to be this comparison that's returning false:
if (( bytes[9-i] > bytes[8-i] + THRESHOLD2 * 10 ))
So then I tried the following, which printed out nothing:
echo ${bytes[9-i]}
Could anyone point me in the right direction? I think the script is nearly done, probably something very simple.
Your shell runs the while read loop in a subshell (see here for why it does not work as expected), so your array magic does not propagate outside the tail | while construct.
Read this and fix accordingly :-)