I was wondering why
i=10
if [ $i -lt 5 ]; then
echo "$i < 5"
elif [ $i -gt 5 ]; then
echo "$i > 5"
elif [ $i -eq 5 ]; then
echo "$i = 5"
fi
Outputs proper result:
10 > 5
Whereas
i=10
[ $i -lt 5 ] && {
echo "$i < 5"
} || [ $i -gt 5 ] && {
echo "$i > 5"
} || [ $i -eq 5 ] && {
echo "$i = 5"
}
behaves an unusual way:
10 > 5
10 = 5
In my opinion, as the interpreter seeks for 1s, it should work like this:
0 && {} || 1 && {} || 0 && {}
0 so the 0 && {} is definitely 0; skip {}
1 means that {} must be checked to define the value of whole 1 && {}
So that the result is 1, but the only {} is executed stays after 1.
However, this all does work as it should when I put ! { instead of {s.
i=10
[ $i -lt 5 ] && ! {
echo "$i < 5"
} || [ $i -gt 5 ] && ! {
echo "$i > 5"
} || [ $i -eq 5 ] && ! {
echo "$i = 5"
}
WHY?! I thought it seeks for 1s so since it finds a 0 in a && it doesn't look at other expressions in the chain!
The {...} does not make a difference, so what you have is equivalent to this:
i=10
[ $i -lt 5 ] &&
echo "$i < 5" ||
[ $i -gt 5 ] &&
echo "$i > 5" ||
[ $i -eq 5 ] &&
echo "$i = 5"
And the way this works is:
[ $i -lt 5 ]: This is false (returns failure), so it jumps to the next ||, which has [ $i -gt 5 ] following it.
[ $i -gt 5 ]: This is true (returns success), so it jumps to the next &&, which has echo "$i > 5" following it.
echo "$i > 5": This returns success, so it jumps to the next &&, which has echo "$i = 5" following it.
echo "$i = 5": This returns success, so it jumps to... wait no, there's a newline. We're done.
&& and || are called short-circuit operators.
EDIT: To stress the point further,
A && B || C
is NOT the same as
if A; then
B
else
C
fi
It's equivalent to
if A; then
if ! B; then
C
fi
else
C
fi
&& and || are evaluated from left to right. Your command is more or less equivalent to this:
(((( false && { echo 1; true; } ) || true ) && { echo 2; true; } ) || false ) && { echo 3; true; }
false && { echo 1; true; } doesn't print anything, and evaluates to false
false || true evaluates to true
true && { echo 2; true; } prints 2 and evaluates to true
true || false evaluates to true
true && { echo 3; true; } prints 3 and evaluates to true.
Mystery solved.
Related
I'm trying to refactor this code:
if [ $(($1 % 4)) -eq 0 ] && [ $(($1 % 100)) -ne 0 ] || [ $(($1 % 400)) -eq 0 ] ; then
echo $T
else
echo $F
fi
into something like this:
if divisibleBy4 && notDivisibleBy100 || divisibleBy400; then
echo $T
else
echo $F
fi
note that
T="true"
F="false"
divisibleBy4 function looks like:
divisibleBy4() {
return [ $(($1 % 4)) -eq 0 ]
}
But I've tried several iterations including what I thought would definitely work.
divisibleBy4() {
if [ $(($1 % 4)) -eq 0 ]; then
return 1
else return 0
fi
}
Any idea how to properly fix the syntax so I can refactor these into functions and use them in my if statement?
When testing I'm seeing the error
syntax error: operand expected (error token is "% 4")
Another thing I tried is, but still doesn't seem to work:
INPUT=$1
divisibleBy4() {
if [ $(($INPUT % 4)) -eq 0 ]; then
return 1
else return 0
fi
}
notDivisibleBy100() {
if [ $(($INPUT % 100)) -ne 0]; then
return 1
else return 0
fi
}
divisibleBy400() {
if [ $(($INPUT % 400)) -eq 0 ]; then
return 1
else return 0
fi
}
if divisibleBy4 && notDivisibleBy100 || divisibleBy400; then
echo $T
else
echo $F
fi
or
INPUT=$1
divisibleBy4() {
return $((!($INPUT %4)))
}
notDivisibleBy100() {
return $(($INPUT %100))
}
divisibleBy400() {
return $((!($INPUT %400)))
}
(( divisibleBy4 && notDivisibleBy100 || divisibleBy400 )) && echo "true" || echo "false"
You want to detect a leap year!
A complete other solution using math mode directly:
a="$1"; (( !(a%4) && a%100 || !(a%400) )) && echo true || echo false
or as if-then-else
a="$1";
if (( !(a%4) && a%100 || !(a%400) )); then
echo true
else
echo false
The simplest, directest answer is to just create functions that consist only of the tests themselves:
INPUT=$1
divisibleBy4() {
[ $(($INPUT % 4)) -eq 0 ]
}
notDivisibleBy100() {
[ $(($INPUT % 100)) -ne 0 ]
}
divisibleBy400() {
[ $(($INPUT % 400)) -eq 0 ]
}
The reason this works is that a function without a return will implicitly return the status of the last command in the function; in these cases, that's the test command (note: [ is a command, even though it doesn't look like one), so the functions just return the result of the test directly.
I'd make at least one change to these, though: they all test the value of the shell variable INPUT; it's much better practice to actually pass the data that functions operate on as parameters. Thus, it'd be better to do something like this:
divisibleBy4() {
[ $(($1 % 4)) -eq 0 ]
}
if divisibleBy4 "$1" ...
Rather than this:
divisibleBy4() {
[ $(($INPUT % 4)) -eq 0 ]
}
INPUT=$1
if divisibleBy4 ...
Note that you can also bundle up the whole leap year check the same way:
isLeapYear() {
[ $(($1 % 4)) -eq 0 ] && [ $(($1 % 100)) -ne 0 ] || [ $(($1 % 400)) -eq 0 ]
}
if isLeapYear "$1"; then
Or use the simpler form #Wiimm suggested:
isLeapYear() {
(( !($1%4) && $1%100 || !($1%400) ))
}
Also, for the shell variables you do use, lower- or mixed-case is preferred, to avoid accidental conflicts with the many all-caps variable names that have special meanings or functions.
Every command sets a result code. If you want to force each calculation to happen in a separate function, you can say
divisibleBy4() {
$((!("$1" % 4)))
}
notDivisibleBy100() {
$(("$1" %100))
}
divisibleBy400() {
$((!("$1" %400)))
}
(divisibleBy4 "$1" &&
notDivisibleBy100 "$1" ||
divisibleBy400 "$1") &&
echo "true" || echo "false"
Breaking up your logic to functions on the subatomic level is not really helping legibility and maintainability, though. Perhaps if you want to make each part reasonably self-domumenting, use comments.
is_leap () {
# Divisible by 4?
(("$1" % 4 == 0)) || return
# Not divisible by 100?
(("$1" % 100 > 0)) && return
# Divisible by 400?
(("$1" % 400 == 0))
}
... Though the comments seem rather superfluous here.
I am using Hacker Rank challenges to teach myself BASH, and I'm in need of some advice.
I'm specifically trying to solve this challenge: Apple and Oranges by nabila_ahmed
I need to read in multiple lines of ints separated by spaces, on multiple lines. I decided to use awk to do this because it seems a lot more efficient in memory storage than using read. (I tried a couple of solutions using read and they timed out, because the test cases are really big.)
Example input:
7 11
5 15
3 2
-2 2 1
5 -6
This is my first attempt in bash and it timed out:
row=0
while read line || [[ -n $line ]]; do
if [ "$row" -eq 0 ]
then
column=0
for n in $line; do
if [ "$column" -eq 0 ]
then
housePos1=$n
elif [ "$column" -eq 1 ]
then
housePos2=$n
fi
((column++))
done
# Calculate house min and max
if [ "$housePos1" -gt "$housePos2" ]
then
minHousePos=$housePos2
maxHousePos=$housePos1
else
minHousePos=$housePos1
maxHousePos=$housePos2
fi
elif [ "$row" -eq 1 ]
then
column=0
for n in $line; do
if [ "$column" -eq 0 ]
then
appleTreePos=$n
elif [ "$column" -eq 1 ]
then
orangeTreePos=$n
fi
((column++))
done
elif [ "$row" -eq 3 ]
then
applesInHouse=0
for n in $line; do
# Calculate the apple's position
let applePos=$((appleTreePos + n))
# If the apple's position is within the houses position, count it
if [ "$applePos" -ge "$minHousePos" ] && [ "$applePos" -le "$maxHousePos" ]
then
((applesInHouse++))
fi
done
elif [ "$row" -eq 4 ]
then
orangesInHouse=0
for n in $line; do
# Calculate the apple's position
let orangePos=$((orangeTreePos + n))
# If the apple's position is within the houses position, count it
if [ "$orangePos" -ge "$minHousePos" ] && [ "$orangePos" -le "$maxHousePos" ]
then
((orangesInHouse++))
fi
done
fi
((row++))
done
echo "$applesInHouse"
echo "$orangesInHouse"
Here is my second attempt in bash, even more of the solutions timed out:
x=0;y=0;read -r s t;read -r a b;read -r m n;
for i in `seq 1 $m`; do
if [ "$i" -lt "$m" ]
then
read -d\ z
else
read -r z
fi
if [ "$((a+z))" -ge "$s" ] && \
[ "$((a+z))" -le "$t" ]
then
((x++))
fi
done
for i in `seq 1 $n`; do
if [ "$i" -lt "$n" ]
then
read -d\ z
else
read -r z
fi
if [ "$((b+z))" -ge "$s" ] && \
[ "$((b+z))" -le "$t" ]
then
((y++))
fi
done
echo $x; echo $y
Here's where I am at in debugging my solution using awk...
awk -v RS='[-]?[0-9]+' \
'{
if(word==$1) {
counter++
if(counter==1){
s=RT
}else if(counter==2){
t=RT
}else if(counter==3){
a=RT
}else if(counter==4){
b=RT
}else if(counter==5){
m=RT
}else if(counter==6){
n=RT
}else{
counter2++
if(counter2<=m){
print "Apples:"
print a+RT
print a+RT>=s
print a+RT<=t
applecount++
}
if(counter2>m && counter2<=m+n){
print "Oranges:"
print b+RT
print b+RT>=s
print b+RT<=t
orangecount++
}
}
}else{
counter=1
word=$1
}
}
END {
print "Total Counts:"
print applecount
print orangecount
}
'
Here is the output from that script when using the sample input
Apples:
3
0
0
Apples:
7
1
0 <-- This is the problem! (7 is less than or equal to 11)
Apples:
6
0
0
Oranges:
20
0
0
Oranges:
9
1
0 <-- This is also a problem! (9 is less than or equal to 11)
Total Counts:
3
2
As you can see, I'm getting some of the wrong comparisons...
ANSWER
(mostly courtesy of #glenn-jackman)
apples_oranges() {
local s t a b m n d
local -a apples oranges
local na=0 nb=0
{
read s t
read a b
read m n
read -a apples
read -a oranges
} < "$1"
for d in "${apples[#]}"; do
(( s <= a+d && a+d <= t )) && ((na++))
done
echo $na
for d in "${oranges[#]}"; do
(( s <= b+d && b+d <= t )) && ((nb++))
done
echo $nb
}
apples_oranges /dev/stdin
I'd do this with bash
apples_oranges() {
local s t a b m n d
local -a apples oranges
local na=0 nb=0
{
read s t
read a b
read m n # unused
read -a apples
read -a oranges
} < "$1"
for d in "${apples[#]}"; do
(( a+d >= s )) && ((na++))
done
echo $na
for d in "${oranges[#]}"; do
(( b-d <= t )) && ((nb++))
done
echo $nb
}
apples_oranges input.txt
this may get you started...
$ awk '
NR==1{split($0,house)}
NR==2{split($0,trees)}
NR==3{split($0,counts)}
NR==4{split($0,apples)}
NR==5{split($0,oranges)}
END{for(i in apples)
if(trees[1]+apples[i]>=house[1] && trees[1]+apples[i]<=house[2]) a++; print a}' file
How to compare two timestamps along with another condition.
Please find below code as an trial for the work around.
d=$(date +%Y%m%d) #Today
d1=$(date +%b" "%d) #Centre1 col 1 & 2 (MON DD)
ct=$(date +'%H%M%S') #Current Time (HHMM)
t01='013000'
t02='033000'
t03='053000'
t04='073000'
find . -mtime 0 -iname "RBDEXT*.csv" -ls | awk '{printf("%-5s%s\t%-40s%s\t%s\t\n", $8,$9,$11,$10,$7)}' > rbdextmp1.txt
rbdextCO=$(wc -l rbdextmp1.txt | awk '{print $1}')
rbdextIN=$(cat rbdextmp1.txt | grep "inprogress" | wc -l)
touch centre.txt
if [[ [ "$rbdextIN" -eq 0 ] &&
[ [ "$ct" -gt "$t01" ] && [ "$ct" -lt "$t02" ] && [ "$rbdextCO" -eq 1 ] ||
[ "$ct" -gt "$t02" ] && [ "$ct" -lt "$t03" ] && [ "$rbdextCO" -eq 2 ] ||
[ "$ct" -gt "$t03" ] && [ "$ct" -lt "$t04" ] && [ "$rbdextCO" -eq 3 ] ]
]]
then
echo "$d1 RBDEXT.$d.csv($rbdextCO) OK" >> centre.txt
elif [ "$rbdextIN" -ge 1 ]
then
echo "$d1 RBDEXT.$d.csv($rbdextCO) OKBUT" >> centre.txt
else
echo "$d1 RBDEXT.$d.csv($rbdextCO) NOK" >> centre.txt
fi
Could you please help me on this please, Thanks a lot !
I suggest you build up to this slowly.
Start with:
if [ "2" -gt "1" ]
then
echo "Green"
else
echo "Red"
fi
... check that it works. Then add a second clause using &&, resolve any syntax problems, make sure it works. Then replace your hard-coded values with variables. Then populate the variables with output from date. Check that it still works after each step. You'll get there.
Bonus tip -- backticks for command substitution have been frowned upon for some time, because it's easy to make mistakes. Use currentTime=$(date +%H%M%S) instead.
It sounds like this is what you want:
currenttime=$(date +'%H%M%S')
dayofweek=$(date +'%u')
time1='013000'
time2='033000'
time3='053000'
time4='073000'
count=$(wc -l < xxx.txt)
if (( (dayofweek == 1) &&
( ( (currenttime > time1) && (currenttime < time2) && (count == 1) ) ||
( (currenttime > time2) && (currenttime < time3) && (count == 2) ) ||
( (currenttime > time3) && (currenttime < time4) && (count == 3) ) )
))
then
color="GREEN"
else
color="RED"
fi
printf 'GP_GLOBAL_FEED(%s) %s\n' "$count" "$color" |
mailx -s "$color" abcd#mail.com
#!/bin/bash
echo "Enter three numbers and this program will give you the middle number : " ; read num1 ; read num2 ; read num3
if [ "$num1" -gt "$num2" ] && [ "$num1" -lt "$num3" ] || [ "$num1" -lt "$num2" ] && [ "$num1" -gt "$num3" ]; then
{
echo "The middle number is $num1"
}
elif [ "$num2" -gt "$num1" ] && [ "$num2" -lt "$num3" ] || [ "$num2" -lt "$num1" ] && [ "$num2" -gt "$num3" ]; then
{
echo "The middle number is $num2"
}
elif [ "$num3" -gt "$num1" ] && [ "$num3" -lt "$num2" ] || [ "$num3 -lt "$num1" ] && [ "$num3" -gt "$num2" ]; then
{ echo "The middle number is $num3" }
fi
The problem I have is with the or condition. I input the numbers 1, 2, and 3, but I get the middle number as 1 all the time.
How about this:
getmid() {
if (( $1 <= $2 )); then
(( $1 >= $3 )) && { echo $1; return; }
(( $2 <= $3 )) && { echo $2; return; }
fi;
if (( $1 >= $2 )); then
(( $1 <= $3 )) && { echo $1; return; }
(( $2 >= $3 )) && { echo $2; return; }
fi;
echo $3;
}
# All permutations of 1, 2 and 3 print 2.
getmid 1 2 3
getmid 2 1 3
getmid 1 3 2
getmid 3 1 2
getmid 2 3 1
getmid 3 2 1
This one should work:
#!/bin/bash
echo "Enter three numbers and this program will give you the middle number : " ; read num1 ; read num2 ; read num3;
if [ "$num1" -gt "$num2" ] && [ "$num1" -lt "$num3" ]; then
{
echo "The middle number is" $num1 ;
}
elif [ "$num1" -lt "$num2" ] && [ "$num1" -gt "$num3" ]; then
{
echo "The middle number is" $num1 ;
}
elif [ "$num2" -gt "$num1" ] && [ "$num2" -lt "$num3" ]; then
{
echo "The middle number is" $num2 ;
}
elif [ "$num2" -lt "$num1" ] && [ "$num2" -gt "$num3" ]; then
{
echo "The middle number is" $num2 ;
}
elif [ "$num3" -gt "$num1" ] && [ "$num3" -lt "$num2" ]; then
{
echo "The middle number is" $num3 ;
}
elif [ "$num3" -lt "$num1" ] && [ "$num3" -gt "$num2" ]; then
{
echo "The middle number is" $num3 ;
}
fi
Related to SO.
fizzy.sh:
#!/usr/bin/env sh
div3() {
expr $1 % 3 = 0
}
div5() {
expr $1 % 5 = 0
}
fizzy() {
if [ $(div3 $1) ] && [ $(div5 $1) ]; then
expr "FizzBuzz"
elif [ $(div3 $1) ]; then
expr "Fizz"
elif [ $(div5 $1) ]; then
expr "Buzz"
else
expr "$1"
fi
}
echo $(fizzy 1)
echo $(fizzy 2)
echo $(fizzy 3)
Example:
$ ./fizzy.sh
FizzBuzz
FizzBuzz
FizzBuzz
expr $1 % 3 = 0 yields 1 or 0, depending on whether the result of $1 % 3 is zero or not, but if treats 0 as true, not false.
sh-3.2$ if [ 0 ]; then echo ok; fi
ok
So you'd need to compare the output of your function against 1. Something like this:
#!/usr/bin/env sh
div3() {
expr $1 % 3 = 0
}
div5() {
expr $1 % 5 = 0
}
fizzy() {
if [ $(div3 $1) -eq 1 ] && [ $(div5 $1) -eq 1 ]; then
expr "FizzBuzz"
elif [ $(div3 $1) -eq 1 ]; then
expr "Fizz"
elif [ $(div5 $1) -eq 1 ]; then
expr "Buzz"
else
expr "$1"
fi
}
for (( i = 1; i <= 15; i++ ))
do
echo $(fizzy $i)
done
Without the need for div3 or div5 functions.
fizzbuzz() { # eg: fizzbuzz 10
((($1%15==0))&& echo FizzBuzz)||
((($1%5==0))&& echo Buzz)||
((($1%3==0))&& echo Fizz)||
echo $1;
}
Or you could do it all at once
fizzbuzz() { # eg: fizzbuzz
for i in {1..100};
do
((($i%15==0))&& echo FizzBuzz)||
((($i%5==0))&& echo Buzz)||
((($i%3==0))&& echo Fizz)||
echo $i;
done;
}
If your shell is bash, you don't need to call out to expr:
div3() { (( $1 % 3 == 0 )); }
div5() { (( $1 % 5 == 0 )); }
fizzbuzz() {
if div3 $1 && div5 $1; then
echo FizzBuzz
elif div3 $1; then
echo Fizz
elif div5 $1; then
echo Buzz
else
echo
fi
}
for ((n=10; n<=15; n++)); do
printf "%d\t%s\n" $n $(fizzbuzz $n)
done