fizz.today

bash ((count++)) kills your set -e script

I wrote a Terraform import script that loops over ~40 SSM parameters and counts how many it imports successfully. Standard stuff:

set -euo pipefail

imported=0

for key in "${KEYS[@]}"; do
  terraform import "aws_ssm_parameter.tenant_config[\"${key}\"]" "/path/${key}"
  ((imported++))
done

echo "Imported: ${imported}"

The first import succeeded. Then the script exited silently. No error, no stack trace, no “Imported: 1.” Just gone.

What bash thinks ((0)) means

Bash arithmetic expressions don’t return whether the operation succeeded. They return whether the result is truthy:

$ ((1)); echo $?
0

$ ((0)); echo $?
1

((0)) evaluates to zero, which bash considers false, which means exit code 1. And set -e treats any non-zero exit code as fatal.

Now look at ((imported++)). The ++ is post-increment — it returns the value before incrementing. When imported is 0:

  1. Post-increment returns 0 (the old value)
  2. imported becomes 1
  3. Bash sees the return value 0, which is falsy
  4. Exit code 1
  5. set -e kills the script

The counter incremented correctly. Bash killed the script anyway.

Three ways to not die

# Suppress the exit code
((imported++)) || true

# Assignment always succeeds regardless of value
imported=$((imported + 1))

# Pre-increment returns the new value, so first call returns 1, not 0
((++imported))

|| true for patching existing code, $((... + 1)) for new code. Pre-increment works but breaks if you ever start from -1.

The seam

(( )) lives between two worlds. In bash, exit code 0 means success. In arithmetic, 0 means false. The (( )) construct maps arithmetic truthiness onto exit codes — zero result becomes non-zero exit code. It’s internally consistent, but it means every (( )) expression that could evaluate to zero is a landmine under set -e.

If you use set -e, audit every (( )) in your scripts. Any expression that can evaluate to zero is a silent exit 1.

#bash #shell #debugging