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?
    – terdon
    Jun 29, 2020 at 15:16

3 Answers 3

4

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
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 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!
    – terdon
    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 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. Jun 29, 2020 at 15:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.