for i in {1..100}
do
if [ ($i % 2) -ne 0 ]
then
echo $i
fi
done
Hi! I am learning Bash but I have some probelems with printing the odd numbers in the range 1 to 100, obviously I have some syntax error which I cannot find.
The {x..y} construct allows a 3rd argument to designate the increment value (default is 1), eg:
for i in {1..20..3} # start with 1 and increment by 3 until you reach/pass 20
do
echo $i
done
This generates:
1
4
7
10
13
16
19
For odd vs even you designate the starting number and increment by 2:
# odd numbers
for i in {1..10..2} # start with an odd number and increment by 2
do
echo $i
done
1
3
5
7
9
# even numbers
for i in {2..10..2} # start with an even number and increment by 2
do
echo $i
done
2
4
6
8
10
I think your if statement isn't correct. Here's a small rewrite with a working if:
#!/bin/bash
for i in {1..100}
do
isEvenIfZero=$i%2;
if [[ $isEvenIfZero -ne 0 ]];
then
#echo -n $i #single line
echo $i
fi
done
Related
it seems single(i) is trying to match with double digit numeric (10 is one of of the value in array)
STATE 1
abc#xyz $ i=1
abc#xyz $ allStatus=(2 3 4 5 6 7 8 9 10)
abc#xyz $ if [[ ! ${allStatus[#]} =~ $i ]]
> then
> echo OK
> fi
STATE 2
abc#xyz $ i=2
abc#xyz $ allStatus=(1 3 4 5 6 7 8 9 10)
abc#xyz $
abc#xyz $ if [[ ! ${allStatus[#]} =~ $i ]]
> then
> echo OK
> fi
OK
abc#xyz $
all i want is, STATE 1 should echo/print OK
That's because the value after =~ is interpreted as a regular expression, and the string 10 matches the regular expression 1.
You need to check that there's a space or string start before the value and space or string end after the value:
[[ ${arr[#]} =~ ( |^)$i( |$) ]]
This can still fail if the value a 1 b belongs to the array:
foStatus=(2 3 'a 1 b')
The correct way is to iterate over the values and check for equality:
arr=(2 3 1 4 9 10 'a 1 b')
i=1
for v in "${arr[#]}" ; do
if [[ $v == "$i" ]] ; then
echo OK
fi
done
Since you are checking a numeric status exist, you can use a sparse array index and do:
#!/usr/bin/env bash
i=2
allStatus=([1]=1 [3]=1 [4]=1 [5]=1 [6]=1 [7]=1 [8]=1 [9]=1 [10]=1)
if ((allStatus[i])); then
echo OK
fi
You can also create a bit-field and check bit is on:
#!/usr/bin/env bash
i=2
allStatus=(1 3 4 5 6 7 8 9 10)
# Set all bits corresponding to status by expanding array elements
# into the arithmetic expression:
# 0 | 1 << element1 | 1 << element2 |...
allStatusBin=$((0${allStatus[#]/#/|1<<}))
# Or set it directly in binary
#allStatusBin=$((2#11111111010))
if ((1<<i&allStatusBin)); then
echo OK
fi
I would like to sequence through a set of numbers.
A simple example is sequence through 2 numbers, then sleep for 5 seconds before proceeding to the next 2.
For example:
1
2
(sleeping for 5 seconds)
3
4
(sleeping for 5 seconds)
Thank you in advance
You can use this for loop in bash:
for ((i=1; i<10; i+=2)); do echo $i $((i+1)); sleep 5; done
1 2
3 4
5 6
7 8
9 10
You could do this as well, using the modulo operator %:
declare -i max_value=10 # set it whatever you want
for ((i = 1; i < $max_value; i++)); do
printf "Number is: $i\n"
[[ $(( $i % 2 )) == 0 ]] && printf "Sleeping\n" && sleep 5 # sleep when the number is even
# do whatever...
done
Output:
Number is: 1
Number is: 2
Sleeping
Number is: 3
Number is: 4
Sleeping
Number is: 5
Number is: 6
Sleeping
Number is: 7
Number is: 8
Sleeping
Number is: 9
If your set of numbers is not a simple set of ascending integers like anubhava's response, you can do similar things.
For example, if your sequence is in a list, list.txt:
declare -ai a=($(<list.txt)); ## Read the sequence from list.txt into a
declare -i i l=${#a[#]};
for ((i=0; i<l; i+=2)); do
echo ${a[i]} ${a[i+1]};
sleep 5;
done;
If your sequence's numbers are in a string, like s='1,2,3,4,5,6,4,32,25,4,6,4', you could do declare -ai a=(${s//,/ }) to fill a with your numbers.
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 am trying to create a multiply table using a for loop but I don't know how to initialize the variables and if the read variable needs to be the same in the for loop
Syntax:
#!/bin/bash
#multiplication table
#$#=parameter given to the script
#$i=variable in for loop
#$1=variable represents a valid number not zero
#$0=variable represents bash script name
echo "Enter number you want to multiply"
read varnumber
echo "This number: $varnumber has this multiplication result:
if [ $varnumber -eq 0 ]
then
echo "Error - Number missing from command line argument"
echo "Syntax : $0 number"
echo "Use to print multiplication table for a given number"
exit 1
fi
n=$varnumber
for i in 1 2 3 4 5 6 7 8 9 10
do
echo "$varnumber * $i = `expr $i \* $varnumber`"
A for loop should end with done, so :
for i in 1 2 3 4 5 6 7 8 9 10
do
echo "$varnumber * $i = `expr $i \* $varnumber`"
done #line added
Also there is no harm in doing :
n="$varnumber"
and note that backticks (` `) is not preferred in bash. Use the command $() format instead for this reason. So :
echo "$varnumber * $i = $(expr $i \* $varnumber)" # Used $() syntax.
See what is obsolete in bash.
In fact, much better if you can finish the job without expr which is an external command:
echo "$varnumber * $i = $((i * varnumber))" # Faster than the previous version
(Thanks #benjamin-w for this suggestion)
A much simpler bash script to do the above goes like this:
#!/bin/bash
echo "Enter the number for which the multiplication table is desired."
read varname
if [ $varname -eq 0 ];
then
echo "Error!! The multiplication of anything with 0 results to 0."
exit 130 # Exit with CTRL + C
fi
for ((i=1; i<=10; i++)); # You can change 10 into any other number
# according to your requirement.
# Or substitute it with a variable (say N)
# which can be prompted to the user.
do
product=$(expr $i \* $varname) # Or use: `expr $i \* $varname`
echo "$varname times $i = $product"
done
The output will look like:
"Enter the number for which the multiplication table is desired."
If I enter 5 when prompted, the following will be printed:
5 times 1 = 5
5 times 2 = 10
5 times 3 = 15
5 times 4 = 20
5 times 5 = 25
5 times 6 = 30
5 times 7 = 35
5 times 8 = 40
5 times 9 = 45
5 times 10 = 50
so I'm dealing with column averaging problems
I have several columns of number,
1 2 3 4 5
2 3 4 5 6
8 4 5 6 7
9 8 7 6 5
1 9 9 9 2
What I want to do is to take an average of each column so that they will yield
4 5 5 6 5
The summing part is not a problem, I'm able to get the sum of each column. However, somehow I'm having trouble in counting the number of columns (variable $x)
Here is my code
while read line; do
x = 0
read -a array <<< "$line"
for i in "${!array[#]}"
do
column[${i}]=$((${column[${i}]}+${array[$i]}))
((x++))
done
done < $TMP
for sum in ${column[#]}
do
average=`expr $sum / $x`
remainder=`expr $sum % $x`
mult=`expr $remainder \* 10`
fracvalue=`expr $mult / $x`
echo $x
echo $average
echo $sum
echo $remainder
echo $mult
echo $fracvalue
done
The last several lines are for my testing purposes, here the $X keeps showing 1 instead of 5. That's why it screws up all the other variable. Does anybody know where's the flaw in this code? Really appreciate your help. Thanks
Another approach:
#!/bin/bash
declare -ia sum # declare array of integers
declare -i lines=0 # declare integer to count lines
# read file to array and sum values
while read -ra array; do
for ((i=0; i<${#array[#]};i++)); do
sum[$i]+=${array[$i]}
done
lines+=1
done < file
# print averages
for ((j=0; j<$i;j++)); do
echo -n "$((${sum[$j]}/$lines)) "
done
Output:
4 5 5 6 5
The problem is in the line
x = 0
should be
x=0 and make ((x+1)) as let "x+=1" it should work.
Maybe your file doesn't have the same number of lines in each column.
while read line; do
read -a array <<< "$line"
for i in "${!array[#]}"
do
column[${i}]=$((${column[${i}]}+${array[$i]}))
((x[${i}]++))
done
done < $TMP
for i in ${!column[#]}
do
sum=${column[$i]}
x=${x[${i}]}
#your calcul
done
The initial issue, aside from a few syntax peculiarities, was the resetting of x on each read which caused you to lose your line count. Additionally, your array is an indexed array, not an associative array.
Making the small adjustments and declareing the variables at the beginning (to hint to bash they are integers or arrays), it works as planned. note: there is no need for a here-string to assign the array, just use regular array syntax. Also note, for indexed arrays, there is no need to dereference the variable within [ ], simply ${array[i]} is fine.
While the use of expr is fine (old, slow, but portable and fine), you could use bash arithmetic syntax for your parameter calculations at the end. (you already have arrays). Finally, you are better served initializing each column[$i]=0 on the first read loop. You can do that with a simple flag:
#!/bin/bash
declare -i ncols=0
declare -i x=0
declare -a column
while read -r line; do
array=( $line )
[ $ncols -eq 0 ] && ncols=${#array[#]}
for ((i = 0; i < ncols; i++))
do
[ $x -eq 0 ] && column[$i]=0
column[$i]=$(( ${column[i]} + ${array[i]}))
done
((x++))
done < "$1"
for sum in ${column[#]}
do
average=`expr $sum / $x`
remainder=`expr $sum % $x`
mult=`expr $remainder \* 10`
fracvalue=`expr $mult / $x`
echo $x
echo $average
echo $sum
echo $remainder
echo $mult
echo $fracvalue
done
exit 0
Output
$ bash colavg.sh dat/colavg.txt
5
4
21
1
10
2
5
5
26
1
10
2
5
5
28
3
30
6
5
6
30
0
0
0
5
5
25
0
0
0