0

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"
7
  • 1
    return is not exactly like some other language's returns. E.g.: ``` divisibleBy4() { if [ $(($1 % 4)) -eq 0 ]; then return 5; else return 17; fi } $(divisibleBy4 4) echo $? # prints 5 $(divisibleBy4 19) echo $? # prints 17 ``` Commented Jan 26, 2019 at 19:36
  • 1
    Your latter version works (though your logic is inverted: 0 should be success), it's just that in a function $1 refers to the function's arguments and not the script's. You'd have to call it as divisibleBy4 "$1". ShellCheck autodetects this issue. Commented Jan 26, 2019 at 19:39
  • @thatotherguy oh that makes sense! But if I add a var INPUT=$1 divisibleBy4() { if [ $(($INPUT % 4)) -eq 0 ]; then return 1 else return 0 fi } doesn't seem to work Commented Jan 26, 2019 at 19:50
  • You seem to imagine that [ is part of the shell's syntax, and/or that the argument to return can be an executable command to evaluate. Both of these are untrue. Commented Jan 26, 2019 at 20:36
  • @tripleee is there no way to execute a command and then use the return of that command to evaluate in an if statement? Commented Jan 26, 2019 at 20:40

3 Answers 3

3

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
Sign up to request clarification or add additional context in comments.

4 Comments

haha you're right! That does look look cleaner - it looks like a%100 would be the opposite of what I want. I get that !(a%4) makes a 0 into a 1 but I guess if it has a remainder like a%100 then it processes as false or a 0?
!x is equal to 1 if x is zero, and 0 if x is any other non-zero value.
Try for ((a=1890;a<2010;a++)); do (( !(a%4) && a%100 || !(a%400) )) && echo $a; done and you see, that the expression is ok.
I see, I get it thanks! Even though this might be a cleaner solution, do you have any insight on the syntax for breaking it up into separate functions so I can call if divisibleBy4 && notDivisibleBy100 || divisibleBy400;
1

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.

Comments

0

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.