How can I create an arithmetic loop in a POSIX shell script?

I have found useful information in Shellcheck.net wiki, I quote:

  1. Bash:

     for ((init; test; next)); do foo; done
    
  2. POSIX:

     : "$((init))"
     while [ "$((test))" -ne 0 ]; do foo; : "$((next))"; done
    

though beware that i++ is not POSIX so would have to be translated, for instance to i += 1 or i = i + 1.

: is a null command that always has a successful exit code. "$((expression))" is an arithmetic expansion that is being passed as an argument to :. You can assign to variables or do arithmetic/comparisons in the arithmetic expansion.


So the above script in the question can be POSIX-wise re-written using those rules like this:

#!/bin/sh
: "$((i=1))"
while [ "$((i != 10))" -ne 0 ]
do
    echo "$i"
    : "$((i = i + 1))"
done

Though here, you can make it more legible with:

#!/bin/sh
i=1
while [ "$i" -ne 10 ]
do
    echo "$i"
    i=$((i + 1))
done

as in init, we're assigning a constant value, so we don't need to evaluate an arithmetic expression. The i != 10 in test can easily be translated to a [ expression, and for next, using a shell variable assignment as opposed to a variable assignment inside an arithmetic expression, lets us get rid of : and the need for quoting.


Beside i++ -> i = i + 1, there are more translations of ksh/bash-specific constructs that are not POSIX that you might have to do:

  • i=1, j=2. The , arithmetic operator is not really POSIX (and conflicts with the decimal separator in some locales with ksh93). You could replace it with another operator like + as in : "$(((i=1) + (j=2)))" but using i=1 j=2 would be a lot more legible.

  • a[0]=1: no arrays in POSIX shells

  • i = 2**20: no power operator in POSIX shell syntax. << is supported though so for powers of two, one can use i = 1 << 20. For other powers, one can resort to bc: i=$(echo "3 ^ 20" | bc)

  • i = RANDOM % 3: not POSIX. The closest in the POSIX toolchest is i=$(awk 'BEGIN{srand(); print int(rand() * 3)}').


thanks for above indepth background knowledge on the difference. A drop in replacement that work for me when using shellcheck.net was as below.

BASH

for i in {1..100}; do  
  ...  
done  

POSIX

i=0; while [ $i -le 100 ]; do  
  ...  
  i=$(( i + 1 ))  
done

some people noted that seq is also an option using seq 1 10 . Creating a loop, however this is dependant that os has seq.