Incrementing a variable triggers EXIT in bash 4, but not in bash 3
Asked Answered
S

1

16

Consider this (exemplary) bash script:

#!/bin/bash -e
errorExit() {
    echo "" >&2
    echo "ERROR (${var_scriptfilename}):" >&2
    echo "An unhandled error occurred." >&2
    intentionalExit 1
}
intentionalExit () {
    trap - EXIT # Unregister the EXIT trap
    exit $1
}
trap errorExit EXIT # Trap script errors
var_scriptfilename="$(basename "$0")"
# ==== START OF TEST ====
var_counter=0
((var_counter++))
echo "var_counter is $var_counter" >&2
# ===== END OF TEST =====
intentionalExit 0

If I run it in Cygwin's bash it produces the intended output:

var_counter is 1

However, if I run it on my Debian Squeeze box, which is its intended destination, I end up in the EXIT trap:

ERROR (test.increment.sh):
An unhandled error occurred.

...Why is that?

If I remove the -e option it works as expected on both systems, but I want to keep -e in use, obviously.

The slightly more cumbersome "universal" variant, var_counter=$(($var_counter+1)), works with -e being set on both shells, but I would prefer to use the first notation (or something similar-looking) since it clearly sticks out as an increment operation when reading the code.

bash --version on the Cygwin bash says:

GNU bash, version 3.2.51(24)-release (i686-pc-cygwin)
Copyright (C) 2007 Free Software Foundation, Inc.

On Debian, it is:

GNU bash, Version 4.1.5(1)-release (x86_64-pc-linux-gnu)
Copyright (C) 2009 Free Software Foundation, Inc.

I am intrigued as to why this is that way. Does anybody know the cause of this behavior?

Also, does anybody know of a similar-looking way to increment a variable in bash that I could use?

Stillborn answered 29/7, 2011 at 17:52 Comment(1)
do you see anything helpful when you set -vx ? Good Luck.Snooperscope
S
24

From the bash4 manpage on Debian:

((expression))
    The expression is evaluated according  to  the  rules  described
    below  under ARITHMETIC EVALUATION.  If the value of the expres‐
    sion is non-zero, the return status is 0; otherwise  the  return
    status is 1.  This is exactly equivalent to let "expression".

and also ...

-e      Exit  immediately  if a pipeline (which may consist of a
        single simple command),  a subshell command enclosed  in
        parentheses,  or one of the commands executed as part of
        a command list enclosed by  braces  (see  SHELL  GRAMMAR
        above) exits with a non-zero status.

So what is happening is ((var++)) increments var from 0 to 1 and returns 0, causing the overall expression to return non-zero, which triggers errexit.

Now for the difference between the two different bash versions: this change in (( behavior seems to have occurred between 4.0 and 4.1. In 4.0 (( apparently did not trigger errexit. See this NEWS file for the details. You'll have to scroll down to line 135 or so. The Changelog from the source distribution seems to confirm this.

If you just want a variable incremented without using the exit status, there's multiple ways to do it. Maybe some other people could give advice on which is the best, but some possibilities are:

  • var="$((var+1))", the portable POSIX sh method
  • ((var++)) || true, forcing the statement to always have a zero exit status (bash only)
Sorrento answered 29/7, 2011 at 19:2 Comment(2)
Thank you very much, that surely sheds a lot of light on that one. I figure, seeing that EXIT is only called because I start counting from 0 on, I can use pre-increment instead of post-increment, i.e. ((++var_counter)) instead of ((var_counter++)). The results of the calculation then start at 1 and the return value will always be zero. I have successfully tested this on both of the mentioned shells now.Stillborn
@FelipeAlvarez (( )) is a standalone command, like executing a statement in other programming languages. (( 2 + 2 )) would just run, returning 0 to the shell. $(( )) is an expansion - the results of the expression are inserted back into the command line in place of the $(( )) expression. It is usually used as part of a longer command line.Sorrento

© 2022 - 2024 — McMap. All rights reserved.