3

I am trying to get the difference of 2 dates in epoch form and convert the number back to days:

EXPIRYEPOCH=$(date --date="$EXPIRYDATE" +%s)
TODAYEPOCH=$(date --date="$TODAYSDATE" +%s)
DAYSLEFT=$(expr ($EXPIRYEPOCH - $TODAYEPOCH) / 86400 )

The DAYSLEFT evaluation fails in the above - whereas a single evaluation of subtraction succeeds in the below:

DAYSLEFT=$(expr $EXPIRYEPOCH - $TODAYEPOCH)

What is the proper formatting to be used to set the DAYSLEFT variable with subtraction followed by division?

1
  • 2
    What do you want to happen if the result of the division isn't an integer? Commented Jun 29, 2020 at 15:16

3 Answers 3

5

Like this (don't use anymore the outdated expr):

dayleft=$(( arithmetic expression ))

If you need float numbers in , use instead:

dayleft=$(bc -l <<< "scale=2; 100/3")

As stated by Stéphane Chazelas in comments, ksh93, zsh and yash do support floating points within $((...)) and ((...)).

expr is a program used in ancient shell code to do math. In Posix shells like bash, use $(( expression )). In bash, ksh88+, mksh/pdksh, or zsh, you can also use (( expression )) or let expression


((...)) is an arithmetic command, which returns an exit status of 0 if the expression is nonzero, or 1 if the expression is zero. Also used as a synonym for "let", if side effects (assignments) are needed. See http://mywiki.wooledge.org/ArithmeticExpression


$((...)) is an arithmetic substitution. After doing the arithmetic, the whole thing is replaced by the value of the expression. See http://mywiki.wooledge.org/ArithmeticExpression


Command Substitution: "$(cmd "foo bar")" causes the command 'cmd' to be executed with the argument 'foo bar' and "$(..)" will be replaced by the output. See http://mywiki.wooledge.org/BashFAQ/002 and http://mywiki.wooledge.org/CommandSubstitution


Avoid using UPPER CASE variables, they are reserved for system use


Finally

expiryepoch=$(date --date="$expirydate" +%s)
todayepoch=$(date --date="$todaysdate" +%s)
dayleft=$(bc <<< "scale=2; (todayepoch - expiryepoch) / 86400")
5
  • Note that ksh93, zsh and yash do support floating points within $((...)) and ((...)). Commented Jun 29, 2020 at 15:56
  • Like this in zsh: print $((100.0/3)) Commented Jun 30, 2020 at 15:43
  • Yes or $((100./3)). Same in yash. In ksh93, it's either $((100./3)) or $((100,/3)) depending on the locale. $((100e0/3)) would work in all 3. In ksh93, you can also do $((float(100)/3)). zsh has that float() math function as well in the zsh/mathfunc module. Commented Jun 30, 2020 at 16:21
  • lists.gnu.org/archive/html/help-bash/2017-10/msg00025.html Commented Jun 30, 2020 at 17:03
  • Yes, or lists.gnu.org/archive/html/bug-bash/2016-02/msg00179.html, the question comes back regularly on both lists. Commented Jun 30, 2020 at 17:33
4

The syntax for arithmetic expansion is $(( math )). So you don't need expr at all here, what you're looking for is:

DAYSLEFT=$(( ($EXPIRYEPOCH - $TODAYEPOCH) / 86400 ))

Now, the reason it failed was because although expr, which is an external program, can read grouped expressions, you need to protect the parentheses from the shell. Since parentheses are reserved characters in the shell (they open and close subshells), if you want to pass a parenthesized statement as is to an external command, you need to escape the parentheses, and also leave a space on either side of them so that they are not concatenated with the variable:

DAYSLEFT=$(expr \( $EXPIRYEPOCH - $TODAYEPOCH \) / 86400 )

Alternatively, you can separate them into two expressions like this:

$ DAYSLEFT=$( expr $(expr $EXPIRYEPOCH - $TODAYEPOCH) / 86400 )
$ echo $DAYSLEFT 
947

Note also that this won't behave very well if the result of the operation isn't an integer.

2
  • "doesn't do multiple operations" That is at least not true for my version: expr (GNU coreutils) 8.31 Commented Jun 29, 2020 at 15:32
  • @HaukeLaging it isn't? I don't really know the first thing about expr, I just quickly checked the manual and it seemed to suggest that was the case, but on closer inspection (and after reading your answer) I see that was wrong. Thanks! Commented Jun 29, 2020 at 15:40
3

You have to escape the brackets, and there must be a space between them and the numbers. Otherwise the shell thinks it is to start a subshell there:

DAYSLEFT=$(expr \( $EXPIRYEPOCH - $TODAYEPOCH \) / 86400 )
2
  • 1
    expr is outdated Commented Jun 29, 2020 at 15:24
  • 6
    @GillesQuenot Maybe but that is the cause of the problem. It is OK to present a better approach but one should at least explain what the actual problem is. Commented Jun 29, 2020 at 15:26

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.