30

I have a bash script that sets -e so the script will exit on any exit status != 0.

I'm trying to do some basic shell arithmetic assigned to variables and sometimes the expression equals 0 which causes the exit status of the let or expr command to be "1".

Here's an example:

#!/bin/bash -ex
echo "Test 1"
Z=`expr 1 - 1` || true
echo "Z will print"
let "A=4 - 4"
echo "A WILL NEVER PRINT $A"
Y=`expr 1 - 1`
echo "Y WILL NEVER PRINT $Y"
X=$(expr 2 - 2)
echo "X WILL NEVER PRINT $X"

The output is:

$ ./test_error.sh 
+ echo 'Test 1'
Test 1
++ expr 1 - 1
+ Z=0
+ true
+ echo 'Z will print'
Z will print
+ let 'A=4 - 4'

My question is what's the idiomatic bash scripting way to allow the script to fail on real exit errors and not on basic arithmetic equaling 0. I could suffix all those expressions with:

A=`expr $C - $D`    || true

But that seems hacky.

Dougnukem
  • 403

4 Answers4

27

Don't use expr for arithmetic. It has long been obsolete: shells now have arithmetic built in, with the $((…)) construct (POSIX), or with let builtin (ksh/bash/zsh) or the ((…)) construct (ksh/bash/zsh).

let and ((…)) return 1 (a failure status code) if the last evaluated expression is 0. To avoid this causing your script to exit under set -e, arrange for the last expression not to return 0, for example:

let "a = 2 - 2" 1
((a = 2 - 2, 1))

Alternatively, use the || true idiom:

((a = 2 - 2)) || true

Alternatively, do your arithmetic inside $((…)) and your assignments outside. An assignment returns the status of the last command substitution in the value, or 0 if there is no command substitution, so you're safe. This has the added benefit of working in any POSIX shell (such as dash).

a=$((2 - 2))
3

I had the same problem. tl;dr:

If the last ARG [of let] evaluates to 0, let returns 1; let returns 0 otherwise.

l0b0
  • 51,350
2

This syntax works for me:

a=$((b + c))
  • This works because arithmetic substitution, unlike command substitution, doesn't affect the return value of the assignment containing it. Which seems oddly inconsistent, but is useful in this case. – Mark Reed Jul 26 '22 at 16:43
0

Use $(( C - D )) instead for your arithmetic. It's more efficient too.

tylerl
  • 2,468
  • 15
  • 18
  • What makes it more efficient than say (( A = $C - $D ))? – bishop Jan 26 '17 at 16:11
  • I'd expect the opposite, if anything. bash has some optimization of the arithmetic support so that it doesn't always have to do conversion back and forth between integers and strings. Gratuitous dollar signs seem like they might force that conversion to happen. – Mark Reed Jul 26 '22 at 16:45
  • See https://unix.stackexchange.com/a/286216/7067 -- (( ... )) evaluates a math expression and can be used in a boolean context (a zero result is false, everything else is true), while $(( ... )) evaluates the expression and returns the result in a way that can be used in an assignment context as if it was a variable. Neither spawns a subshell or executes another command, so they're both efficient. The difference between A=$(( $C - $D )) vs (( A = $C - $D )) is that the latter will ALSO set $? to 0 or 1 depending on whether A was set to zero or not. – tylerl Aug 31 '22 at 16:20
  • Also, relevant to the question at hand, (( A = $C - $D )) will cause the script to exit if A gets set to zero when running the script under a set -e context. That's kinda the whole point of OP's question. – tylerl Aug 31 '22 at 16:29